diff options
Diffstat (limited to '')
-rw-r--r-- | examples/todos/src/main.rs | 57 | ||||
-rw-r--r-- | native/src/widget/operation.rs | 93 | ||||
-rw-r--r-- | src/lib.rs | 6 | ||||
-rw-r--r-- | src/widget.rs | 11 |
4 files changed, 154 insertions, 13 deletions
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 5cbb6228..25d90a0b 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -1,12 +1,15 @@ use iced::alignment::{self, Alignment}; +use iced::event::{self, Event}; +use iced::keyboard; +use iced::subscription; use iced::theme::{self, Theme}; use iced::widget::{ - button, checkbox, column, container, row, scrollable, text, text_input, - Text, + self, button, checkbox, column, container, row, scrollable, text, + text_input, Text, }; use iced::window; use iced::{Application, Element}; -use iced::{Color, Command, Font, Length, Settings}; +use iced::{Color, Command, Font, Length, Settings, Subscription}; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; @@ -48,6 +51,7 @@ enum Message { CreateTask, FilterChanged(Filter), TaskMessage(usize, TaskMessage), + TabPressed, } impl Application for Todos { @@ -94,11 +98,12 @@ impl Application for Todos { } Todos::Loaded(state) => { let mut saved = false; - let mut task_command = Command::none(); - match message { + let command = match message { Message::InputChanged(value) => { state.input_value = value; + + Command::none() } Message::CreateTask => { if !state.input_value.is_empty() { @@ -107,29 +112,44 @@ impl Application for Todos { .push(Task::new(state.input_value.clone())); state.input_value.clear(); } + + Command::none() } Message::FilterChanged(filter) => { state.filter = filter; + + Command::none() } Message::TaskMessage(i, TaskMessage::Delete) => { state.tasks.remove(i); + + Command::none() } Message::TaskMessage(i, task_message) => { if let Some(task) = state.tasks.get_mut(i) { - if matches!(task_message, TaskMessage::Edit) { - task_command = - text_input::focus(Task::text_input_id(i)); - } + let should_focus = + matches!(task_message, TaskMessage::Edit); task.update(task_message); + + if should_focus { + text_input::focus(Task::text_input_id(i)) + } else { + Command::none() + } + } else { + Command::none() } } Message::Saved(_) => { state.saving = false; saved = true; + + Command::none() } - _ => {} - } + Message::TabPressed => widget::focus_next(), + _ => Command::none(), + }; if !saved { state.dirty = true; @@ -152,7 +172,7 @@ impl Application for Todos { Command::none() }; - Command::batch(vec![task_command, save]) + Command::batch(vec![command, save]) } } } @@ -225,6 +245,19 @@ impl Application for Todos { } } } + + fn subscription(&self) -> Subscription<Message> { + subscription::events_with(|event, status| match (event, status) { + ( + Event::Keyboard(keyboard::Event::KeyPressed { + key_code: keyboard::KeyCode::Tab, + .. + }), + event::Status::Ignored, + ) => Some(Message::TabPressed), + _ => None, + }) + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index 2cfba005..5a0f0c18 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -58,3 +58,96 @@ pub fn focus<T>(target: Id) -> impl Operation<T> { Focus { target } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct FocusCount { + focused: Option<usize>, + total: usize, +} + +pub fn count_focusable<T, O>(f: fn(FocusCount) -> O) -> impl Operation<T> +where + O: Operation<T> + 'static, +{ + struct CountFocusable<O> { + count: FocusCount, + next: fn(FocusCount) -> O, + } + + impl<T, O> Operation<T> for CountFocusable<O> + where + O: Operation<T> + 'static, + { + fn focusable( + &mut self, + state: &mut dyn state::Focusable, + _id: Option<&Id>, + ) { + if state.is_focused() { + self.count.focused = Some(self.count.total); + } + + self.count.total += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ) { + operate_on_children(self) + } + + fn finish(&self) -> Outcome<T> { + Outcome::Chain(Box::new((self.next)(self.count))) + } + } + + CountFocusable { + count: FocusCount::default(), + next: f, + } +} + +pub fn focus_next<T>() -> impl Operation<T> { + struct FocusNext { + count: FocusCount, + current: usize, + } + + impl<T> Operation<T> for FocusNext { + fn focusable( + &mut self, + state: &mut dyn state::Focusable, + _id: Option<&Id>, + ) { + if self.count.total == 0 { + return; + } + + match self.count.focused { + None if self.current == 0 => state.focus(), + Some(focused) if focused == self.current => state.unfocus(), + Some(focused) if focused + 1 == self.current => state.focus(), + Some(focused) + if focused == self.count.total - 1 && self.current == 0 => + { + state.focus() + } + _ => {} + } + + self.current += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>), + ) { + operate_on_children(self) + } + } + + count_focusable(|count| FocusNext { count, current: 0 }) +} @@ -192,22 +192,26 @@ use iced_wgpu as renderer; use iced_glow as renderer; pub use iced_native::theme; +pub use runtime::event; +pub use runtime::subscription; pub use application::Application; pub use element::Element; pub use error::Error; +pub use event::Event; pub use executor::Executor; pub use renderer::Renderer; pub use result::Result; pub use sandbox::Sandbox; pub use settings::Settings; +pub use subscription::Subscription; pub use theme::Theme; pub use runtime::alignment; pub use runtime::futures; pub use runtime::{ Alignment, Background, Color, Command, ContentFit, Font, Length, Padding, - Point, Rectangle, Size, Subscription, Vector, + Point, Rectangle, Size, Vector, }; #[cfg(feature = "system")] diff --git a/src/widget.rs b/src/widget.rs index abffadd5..2333aa28 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -211,3 +211,14 @@ pub use qr_code::QRCode; #[cfg(feature = "svg")] #[cfg_attr(docsrs, doc(cfg(feature = "svg")))] pub use svg::Svg; + +use crate::Command; +use iced_native::widget::operation; + +/// Focuses the next focusable widget. +pub fn focus_next<Message>() -> Command<Message> +where + Message: 'static, +{ + Command::widget(operation::focus_next()) +} |