-
Notifications
You must be signed in to change notification settings - Fork 487
Control cursor position and visibility via Frame #309
Conversation
Actually the cursor is not (and should not be) part of what is being drawn. |
I think a better way would be to make it possible to "query" the widgets, so that we are able to calculate the position of the cursor after draw. Something of the sort would be needed for #166 anyway... Taking inspiration from QtQuick, perhaps we could switch to a scene graph-based approach? (It's still immediate mode, if I understand correctly.) |
I think that's debatable. When the cursor is visible, it becomes a part of the "picture", which is
Widgets in tui-rs are destroyed after each draw, so I don't see how they can be queried after the fact. Perhaps their state could be queried? I agree that cursor positioning can be decoupled from drawing, but I wonder if we should. Currently main loop might look like |
src/terminal.rs
Outdated
@@ -162,6 +210,10 @@ where | |||
self.buffers[1 - self.current].reset(); | |||
self.buffers[1 - self.current].resize(area); | |||
self.known_size = area; | |||
if let CursorState::Shown { x, y } = self.cursor_state_after_drawing { | |||
let (x, y) = clip(self.known_size, (x, y)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the crate usually does not attempt to fix silently users' errors. In this case if the cursor is outside of the viewport then it should simply not be shown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I explicitly check for that and hide the cursor, or is it okay to just pass the coordinates down to Terminal::set_cursor
, and let the backend deal with it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went ahead and changed it so it just passes the coordinates down into Terminal::set_cursor()
; see 65db02a.
@Minoru I would advocate for a breaking change if that's make the api and the internal logic better. Moreover, I fail to see how this would be painful for end users other than changing some functions signatures. I would also not change the cursor visibility based on a value changed in the @teohhanhui I tend to agree with @Minoru on this. The cursor may be visible so it can and is considered by some users as part of the UI. This change should end up adding a single utility function that make this cursor position easier to manipulate. The cursor is not actually moved during the draw call but you register its desired position after it. If you have a strong argument against this I'm more than happy to hear it. |
But then we're back to square one: the user can set the position from their
Okay! Re-doing it now. |
Actually I still don't understand what benefit this PR brings. For me as a user, I'd like to be able to say, "set the cursor in this widget, at (col, row)", for example when I'm doing a "text field". So, for example: let input_texts = vec![Text::raw(input_value.as_str())];
let input_paragraph = Paragraph::new(input_texts.iter())
.cursor(input_value.chars().count(), 0); Currently I have to store the area |
Let's run with your example. It might make sense to add
That a different problem. You can solve it by creating a new This PR resolves different problem: you can't change cursor's position from the function that renders your UI. You have to either capture a reference to |
Hmm, yeah, you're right. This does open up the possibility of implementing the
I wouldn't do that because it's only a half solution, but I don't want to go off-topic too much, and it's already a solved problem in my app (by setting the cursor after draw). |
Upon re-reading the code I found that Apart from addressing the feedback, I fixed two things:
I did those as fixup commits to make it easier to review, but I'd like a chance to squash them before you merge. |
@Minoru I continue to believe that storing the cursor on the I'm also starting to wonder whether we could deprecate |
That sounds much better than what I have envisioned! I updated the code to match.
Well, since the new API is a breaking change, I removed the old API instead of deprecating it. It's a separate commit, so can be easily dropped if need be. |
crossterm_demo doesn't compile if I remove |
Cargo.toml
Outdated
@@ -1,11 +1,11 @@ | |||
[package] | |||
name = "tui" | |||
version = "0.9.5" | |||
version = "0.10.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should not change the version, I'll take care of that when I release.
src/terminal.rs
Outdated
/// | ||
/// If `None`, the cursor is hidden and its position is controlled by the backend. If `Some((x, | ||
/// y))`, the cursor is shown and placed at `(x, y)` after the call to `Terminal::draw()`. | ||
resulting_cursor_position: Option<(u16, u16)>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resulting_cursor_position: Option<(u16, u16)>, | |
cursor_position: Option<(u16, u16)>, |
given that there is no other cursor position, I think that we can drop the adjective.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to emphasize that this position will become effective only after the frame is drawn, but I guess stating that in the docstring is enough.
I've left some very last comments but I see nothing blocking the merge. Thank you for keeping with the long review process and sharing your thoughtful view on this subject. I think it will be a neat addition to this crate. |
Both comments are now addressed. Thanks for the review! |
This implements the idea I outlined in #91 (comment):
The implementation deviates from the idea in two ways.
First of all,
Frame
is moved into the drawing closure — there is no way forTerminal::draw()
to examine frame's fields afterwards. This could be resolved in two ways:make
Terminal::draw()
requireFnOnce(&mut Frame<B>)
. This is very clean, but it's a breaking change, so I decided against that.store that
Option<(u16, u16)>
in theTerminal
itself, and makeFrame
just pass the calls through. That's a bit of a hack, because I don't think it'sTerminal
's responsibility to store this data; it'sFrame
's job to describe what has to be drawn and how.The second option is a non-breaking change, so I went with that.
That decision led to the second deviation.
Terminal
already has an API for controlling the cursor:hide_cursor()
,show_cursor()
,set_cursor()
. These calls have immediate effect on the terminal (modulo stdout buffering). This conflicts with the proposed API, and could be resolved in two ways:make the old API non-public. This doesn't seem right to me, since
Terminal
is supposed to be a low-level wrapper around the backend.make the new API opt-in. Up until the first call to
Frame::set_desired_cursor_position()
, calls todraw()
don't affect cursor visibility, so the user is free to useTerminal::hide_cursor()
andTerminal::show_cursor()
. After the first call to the new API, however, the cursor position and visibility is always enforced after eachTerminal::draw()
call.The first option is not viable, so I went with the second one.
I think that in the long term, tui-rs should change the signature of
Terminal::draw()
, to move all of this toFrame
, and makeTerminal
a completely low-level thing again. If that's the plan, it might make sense to make the new API onTerminal
non-public now, so people don't depend on what is going to be removed. @fdehau, I'm also okay with re-doing the PR to change the signature now, if you're ok with a breaking change.