Move focus between text fields with tab and shift-tab

This commit is contained in:
Emil Ernerfeldt 2020-11-15 18:10:38 +01:00
parent b17e6b3260
commit e7fd11f1aa
4 changed files with 93 additions and 37 deletions

61
TODO.md
View file

@ -4,7 +4,8 @@ TODO-list for the Egui project. If you looking for something to do, look here.
## Top priority
* Refactor graphics layers and areas so one don't have to register LayerId:s.
* Egui-web copy-paste
* Egui-web fetch
## Other
@ -13,14 +14,6 @@ TODO-list for the Egui project. If you looking for something to do, look here.
* [ ] Tooltip widget: Something that looks like this: (?) :that shows text on hover.
* [ ] ui.info_button().on_hover_text("More info here");
* [ ] Allow adding multiple tooltips to the same widget, showing them all one after the other.
* [ ] Text input
* [x] Input
* [x] Text focus
* [x] Cursor movement
* [x] Text selection
* [x] Clipboard copy/paste
* [ ] Text edit undo
* [ ] Move focus with tab
* [ ] Vertical slider
* [/] Color picker
* [x] linear rgb <-> sRGB
@ -32,21 +25,13 @@ TODO-list for the Egui project. If you looking for something to do, look here.
* [ ] Additive blending aware color picker
* [ ] Premultiplied alpha is a bit of a pain in the ass. Maybe rethink this a bit.
* [ ] Hue wheel
* Containers
* [ ] Scroll areas
* [x] Vertical scrolling
* [x] Scroll-wheel input
* [x] Drag background to scroll
* [x] Kinetic scrolling
* [ ] Horizontal scrolling
* Input
* [x] Distinguish between clicks and drags
* [x] Double-click
* [x] Text
* [x] Get modifier keys
* [x] Modifier keys
* [ ] Support all mouse buttons
* [ ] Distinguish between touch input and mouse input
* [ ] Keyboard shortcuts (copy, paste, undo, select-all, ...?)
* Text
* [/] Unicode
* [x] Shared mutable expanding texture map
@ -91,26 +76,14 @@ TODO-list for the Egui project. If you looking for something to do, look here.
* [ ] Ask Egui if an event requires repainting
* [ ] Only repaint when mouse is over a Egui window (or is pressed and there is an active widget)
## Backends
* [ ] Extract egui_app as egui_backend
* egui_glium
* egui_web
* [ ] async HTTP requests
* [ ] egui_bitmap: slow reference rasterizer for tests
* Port https://github.com/emilk/imgui_software_renderer
* Less important: fast rasterizer for embedded 🤷‍♀️
* [ ] egui_terminal (think ncurses)
* [ ] replace `round_to_pixel` with `round_to_X` where user can select X to be e.g. width of a letter
* [ ] egui_svg: No idea what this would be for :)
## Integrations
### egui_web
* [x] Scroll input
* [x] Change to resize cursor on hover
* [x] Port most code to Rust
* [x] Read url fragment and redirect to a subpage (e.g. different examples apps)]
* [ ] Copy/paste support
* [ ] Async HTTP requests
* [ ] Fix WebGL colors/blending (try EXT_sRGB)
* [ ] Embeddability
@ -121,6 +94,17 @@ TODO-list for the Egui project. If you looking for something to do, look here.
* Different Egui instances, same app
* Allows very nice web integration
### Other
* [ ] Extract egui::app as own library (egui_framework ?)
* [ ] egui_bitmap: slow reference rasterizer for tests
* Port https://github.com/emilk/imgui_software_renderer
* Less important: fast rasterizer for embedded 🤷‍♀️
* [ ] egui_terminal (think ncurses)
* [ ] replace `round_to_pixel` with `round_to_X` where user can select X to be e.g. width of a letter
* [ ] egui_svg: No idea what this would be for :)
## Modularity
* [x] `trait Widget` (`Label`, `Slider`, `Checkbox`, ...)
@ -163,13 +147,24 @@ Ability to do a search for any widget. The search works even for collapsed regio
* [x] Collapsing header region
* [x] Tooltip
* [x] Movable/resizable windows
* [x] Kinetic windows
* [x] Add support for clicking hyperlinks
* [x] Text input
* [x] Input
* [x] Text focus
* [x] Cursor movement
* [x] Text selection
* [x] Clipboard copy/paste
* [x] Move focus with tab
* [x] Text edit undo
* Containers
* [x] Vertical slider
* [x] Resize any side and corner on windows
* [x] Fix autoshrink
* [x] Automatic positioning of new windows
* [x] Vertical scroll areas
* [x] Scroll-wheel input
* [x] Drag background to scroll
* [x] Kinetic scrolling
* Simple animations
* Clip rects
* [x] Separate Ui::clip_rect from Ui::rect

View file

@ -205,7 +205,7 @@ impl Context {
}
fn begin_frame_mut(&mut self, new_raw_input: RawInput) {
self.memory().begin_frame(&self.input);
self.memory().begin_frame(&self.input, &new_raw_input);
self.input = std::mem::take(&mut self.input).begin_frame(new_raw_input);
*self.available_rect.lock() = Some(self.input.screen_rect());

View file

@ -83,6 +83,18 @@ pub(crate) struct Interaction {
/// What had keyboard focus previous frame?
pub kb_focus_id_previous_frame: Option<Id>,
/// If set, the next widget that is interested in kb_focus will automatically get it.
/// Probably because the user pressed Tab.
pub kb_focus_give_to_next: bool,
/// The last widget interested in kb focus.
pub kb_focus_last_interested: Option<Id>,
/// Set at the beginning of the frame, set to `false` when "used".
pressed_tab: bool,
/// Set at the beginning of the frame, set to `false` when "used".
pressed_shift_tab: bool,
/// HACK: windows have low priority on dragging.
/// This is so that if you drag a slider in a window,
/// the slider will steal the drag away from the window.
@ -104,7 +116,11 @@ impl Interaction {
self.click_id.is_some() || self.drag_id.is_some()
}
fn begin_frame(&mut self, prev_input: &crate::input::InputState) {
fn begin_frame(
&mut self,
prev_input: &crate::input::InputState,
new_input: &crate::input::RawInput,
) {
self.kb_focus_id_previous_frame = self.kb_focus_id;
self.click_interest = false;
self.drag_interest = false;
@ -118,13 +134,34 @@ impl Interaction {
self.click_id = None;
self.drag_id = None;
}
self.pressed_tab = false;
self.pressed_shift_tab = false;
for event in &new_input.events {
if let crate::input::Event::Key {
key: crate::input::Key::Tab,
pressed: true,
modifiers,
} = event
{
if modifiers.shift {
self.pressed_shift_tab = true;
} else {
self.pressed_tab = true;
}
}
}
}
}
impl Memory {
pub(crate) fn begin_frame(&mut self, prev_input: &crate::input::InputState) {
pub(crate) fn begin_frame(
&mut self,
prev_input: &crate::input::InputState,
new_input: &crate::input::RawInput,
) {
self.used_ids.clear();
self.interaction.begin_frame(prev_input);
self.interaction.begin_frame(prev_input, new_input);
if !prev_input.mouse.down {
self.window_interaction = None;
@ -169,6 +206,26 @@ impl Memory {
}
}
/// Register this widget as being interested in getting keyboard focus.
/// This will allow the user to select it with tab and shift-tab.
pub fn interested_in_kb_focus(&mut self, id: Id) {
if self.interaction.kb_focus_give_to_next {
self.interaction.kb_focus_id = Some(id);
self.interaction.kb_focus_give_to_next = false;
} else if self.has_kb_focus(id) {
if self.interaction.pressed_tab {
self.interaction.kb_focus_id = None;
self.interaction.kb_focus_give_to_next = true;
self.interaction.pressed_tab = false;
} else if self.interaction.pressed_shift_tab {
self.interaction.kb_focus_id = self.interaction.kb_focus_last_interested;
self.interaction.pressed_shift_tab = false;
}
}
self.interaction.kb_focus_last_interested = Some(id);
}
/// Stop editing of active `TextEdit` (if any).
pub fn stop_text_input(&mut self) {
self.interaction.kb_focus_id = None;

View file

@ -252,6 +252,10 @@ impl<'t> Widget for TextEdit<'t> {
};
let response = ui.interact(rect, id, sense);
if enabled {
ui.memory().interested_in_kb_focus(id);
}
if enabled {
if let Some(mouse_pos) = ui.input().mouse.pos {
// TODO: triple-click to select whole paragraph