Implement focus_next operation
... as well as a `count_focusable` composable helper!
This commit is contained in:
parent
6dac049db5
commit
77c6864e7c
4 changed files with 154 additions and 13 deletions
|
|
@ -1,12 +1,15 @@
|
||||||
use iced::alignment::{self, Alignment};
|
use iced::alignment::{self, Alignment};
|
||||||
|
use iced::event::{self, Event};
|
||||||
|
use iced::keyboard;
|
||||||
|
use iced::subscription;
|
||||||
use iced::theme::{self, Theme};
|
use iced::theme::{self, Theme};
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, checkbox, column, container, row, scrollable, text, text_input,
|
self, button, checkbox, column, container, row, scrollable, text,
|
||||||
Text,
|
text_input, Text,
|
||||||
};
|
};
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced::{Application, Element};
|
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 lazy_static::lazy_static;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -48,6 +51,7 @@ enum Message {
|
||||||
CreateTask,
|
CreateTask,
|
||||||
FilterChanged(Filter),
|
FilterChanged(Filter),
|
||||||
TaskMessage(usize, TaskMessage),
|
TaskMessage(usize, TaskMessage),
|
||||||
|
TabPressed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Todos {
|
impl Application for Todos {
|
||||||
|
|
@ -94,11 +98,12 @@ impl Application for Todos {
|
||||||
}
|
}
|
||||||
Todos::Loaded(state) => {
|
Todos::Loaded(state) => {
|
||||||
let mut saved = false;
|
let mut saved = false;
|
||||||
let mut task_command = Command::none();
|
|
||||||
|
|
||||||
match message {
|
let command = match message {
|
||||||
Message::InputChanged(value) => {
|
Message::InputChanged(value) => {
|
||||||
state.input_value = value;
|
state.input_value = value;
|
||||||
|
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::CreateTask => {
|
Message::CreateTask => {
|
||||||
if !state.input_value.is_empty() {
|
if !state.input_value.is_empty() {
|
||||||
|
|
@ -107,29 +112,44 @@ impl Application for Todos {
|
||||||
.push(Task::new(state.input_value.clone()));
|
.push(Task::new(state.input_value.clone()));
|
||||||
state.input_value.clear();
|
state.input_value.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::FilterChanged(filter) => {
|
Message::FilterChanged(filter) => {
|
||||||
state.filter = filter;
|
state.filter = filter;
|
||||||
|
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::TaskMessage(i, TaskMessage::Delete) => {
|
Message::TaskMessage(i, TaskMessage::Delete) => {
|
||||||
state.tasks.remove(i);
|
state.tasks.remove(i);
|
||||||
|
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
Message::TaskMessage(i, task_message) => {
|
Message::TaskMessage(i, task_message) => {
|
||||||
if let Some(task) = state.tasks.get_mut(i) {
|
if let Some(task) = state.tasks.get_mut(i) {
|
||||||
if matches!(task_message, TaskMessage::Edit) {
|
let should_focus =
|
||||||
task_command =
|
matches!(task_message, TaskMessage::Edit);
|
||||||
text_input::focus(Task::text_input_id(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
task.update(task_message);
|
task.update(task_message);
|
||||||
|
|
||||||
|
if should_focus {
|
||||||
|
text_input::focus(Task::text_input_id(i))
|
||||||
|
} else {
|
||||||
|
Command::none()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Saved(_) => {
|
Message::Saved(_) => {
|
||||||
state.saving = false;
|
state.saving = false;
|
||||||
saved = true;
|
saved = true;
|
||||||
|
|
||||||
|
Command::none()
|
||||||
}
|
}
|
||||||
_ => {}
|
Message::TabPressed => widget::focus_next(),
|
||||||
}
|
_ => Command::none(),
|
||||||
|
};
|
||||||
|
|
||||||
if !saved {
|
if !saved {
|
||||||
state.dirty = true;
|
state.dirty = true;
|
||||||
|
|
@ -152,7 +172,7 @@ impl Application for Todos {
|
||||||
Command::none()
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -58,3 +58,96 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {
|
||||||
|
|
||||||
Focus { target }
|
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;
|
use iced_glow as renderer;
|
||||||
|
|
||||||
pub use iced_native::theme;
|
pub use iced_native::theme;
|
||||||
|
pub use runtime::event;
|
||||||
|
pub use runtime::subscription;
|
||||||
|
|
||||||
pub use application::Application;
|
pub use application::Application;
|
||||||
pub use element::Element;
|
pub use element::Element;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
pub use event::Event;
|
||||||
pub use executor::Executor;
|
pub use executor::Executor;
|
||||||
pub use renderer::Renderer;
|
pub use renderer::Renderer;
|
||||||
pub use result::Result;
|
pub use result::Result;
|
||||||
pub use sandbox::Sandbox;
|
pub use sandbox::Sandbox;
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
|
pub use subscription::Subscription;
|
||||||
pub use theme::Theme;
|
pub use theme::Theme;
|
||||||
|
|
||||||
pub use runtime::alignment;
|
pub use runtime::alignment;
|
||||||
pub use runtime::futures;
|
pub use runtime::futures;
|
||||||
pub use runtime::{
|
pub use runtime::{
|
||||||
Alignment, Background, Color, Command, ContentFit, Font, Length, Padding,
|
Alignment, Background, Color, Command, ContentFit, Font, Length, Padding,
|
||||||
Point, Rectangle, Size, Subscription, Vector,
|
Point, Rectangle, Size, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "system")]
|
#[cfg(feature = "system")]
|
||||||
|
|
|
||||||
|
|
@ -211,3 +211,14 @@ pub use qr_code::QRCode;
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "svg")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "svg")))]
|
||||||
pub use svg::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())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue