summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/todos/src/main.rs57
-rw-r--r--native/src/widget/operation.rs93
-rw-r--r--src/lib.rs6
-rw-r--r--src/widget.rs11
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 })
+}
diff --git a/src/lib.rs b/src/lib.rs
index 38ba48be..100b9f77 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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())
+}