Last active
March 10, 2024 10:39
-
-
Save mtkennerly/edea51b6c35b1cb85421107070cc7569 to your computer and use it in GitHub Desktop.
Iced 0.12.1 issue with Undoable widget
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "iced-undoable" | |
version = "0.1.0" | |
edition = "2021" | |
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |
[dependencies] | |
iced = { version = "0.12.1", features = ["advanced"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mod undoable; | |
use iced::widget::{column, text_input}; | |
use iced::{Alignment, Element, Sandbox, Settings}; | |
use undoable::{Action, Undoable}; | |
pub fn main() -> iced::Result { | |
Counter::run(Settings::default()) | |
} | |
struct Counter { | |
history: Vec<String>, | |
index: usize, | |
} | |
#[derive(Debug, Clone)] | |
enum Message { | |
Input(String), | |
Revert(Action), | |
} | |
impl Sandbox for Counter { | |
type Message = Message; | |
fn new() -> Self { | |
Self { | |
history: vec!["!".to_string()], | |
index: 0, | |
} | |
} | |
fn title(&self) -> String { | |
String::from("Repro") | |
} | |
fn update(&mut self, message: Message) { | |
println!("Message: {:?}", &message); | |
match message { | |
Message::Input(text) => { | |
self.history.truncate(self.index + 1); | |
self.history.push(text); | |
self.index = self.history.len() - 1; | |
} | |
Message::Revert(action) => { | |
match action { | |
Action::Undo => { | |
self.index = self.index.saturating_sub(1); | |
} | |
Action::Redo => { | |
self.index = (self.index + 1).min(self.history.len().saturating_sub(1)); | |
} | |
} | |
} | |
} | |
println!("New text: {}", &self.history[self.index]); | |
} | |
fn view(&self) -> Element<Message> { | |
column![ | |
Undoable::new( | |
text_input("", &self.history[self.index]).on_input(Message::Input), | |
Message::Revert, | |
) | |
] | |
.padding(20) | |
.align_items(Alignment::Center) | |
.into() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use iced::{ | |
advanced::{ | |
layout, renderer, | |
widget::{Operation, Tree}, | |
Clipboard, Layout, Shell, Widget, | |
}, | |
event::{self, Event}, | |
keyboard::Key, | |
mouse, overlay, Element, Length, Rectangle, | |
}; | |
#[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
pub enum Action { | |
Undo, | |
Redo, | |
} | |
#[allow(missing_debug_implementations)] | |
pub struct Undoable<'a, Message, Theme, Renderer, F> | |
where | |
Message: Clone, | |
F: Fn(Action) -> Message + 'a, | |
{ | |
content: Element<'a, Message, Theme, Renderer>, | |
on_change: F, | |
} | |
impl<'a, Message, Theme, Renderer, F> Undoable<'a, Message, Theme, Renderer, F> | |
where | |
Message: Clone, | |
F: Fn(Action) -> Message + 'a, | |
{ | |
pub fn new<T>(content: T, on_change: F) -> Self | |
where | |
T: Into<Element<'a, Message, Theme, Renderer>>, | |
{ | |
Self { | |
content: content.into(), | |
on_change, | |
} | |
} | |
} | |
impl<'a, Message, Theme, Renderer, F> Widget<Message, Theme, Renderer> for Undoable<'a, Message, Theme, Renderer, F> | |
where | |
Message: Clone, | |
Renderer: iced::advanced::text::Renderer, | |
F: Fn(Action) -> Message + 'a, | |
{ | |
fn children(&self) -> Vec<Tree> { | |
vec![Tree::new(&self.content)] | |
} | |
fn diff(&self, tree: &mut Tree) { | |
tree.diff_children(std::slice::from_ref(&self.content)) | |
} | |
fn size(&self) -> iced::Size<Length> { | |
self.content.as_widget().size() | |
} | |
fn size_hint(&self) -> iced::Size<Length> { | |
self.content.as_widget().size_hint() | |
} | |
fn state(&self) -> iced::advanced::widget::tree::State { | |
self.content.as_widget().state() | |
} | |
fn tag(&self) -> iced::advanced::widget::tree::Tag { | |
self.content.as_widget().tag() | |
} | |
fn layout( | |
&self, | |
tree: &mut Tree, | |
renderer: &Renderer, | |
limits: &layout::Limits, | |
) -> layout::Node { | |
self.content.as_widget().layout(tree, renderer, limits) | |
} | |
fn operate( | |
&self, | |
tree: &mut Tree, | |
layout: Layout<'_>, | |
renderer: &Renderer, | |
operation: &mut dyn Operation<Message>, | |
) { | |
self.content | |
.as_widget() | |
.operate(&mut tree.children[0], layout, renderer, operation) | |
} | |
fn on_event( | |
&mut self, | |
tree: &mut Tree, | |
event: Event, | |
layout: Layout<'_>, | |
cursor: mouse::Cursor, | |
renderer: &Renderer, | |
clipboard: &mut dyn Clipboard, | |
shell: &mut Shell<'_, Message>, | |
viewport: &Rectangle, | |
) -> event::Status { | |
if let Event::Keyboard(iced::keyboard::Event::KeyPressed { key, modifiers, .. }) = &event { | |
let focused = tree.children[0] | |
.state | |
.downcast_ref::<iced::widget::text_input::State::<Renderer::Paragraph>>() | |
.is_focused(); | |
if focused { | |
match (key.as_ref(), modifiers.command(), modifiers.shift()) { | |
(Key::Character("z"), true, false) => { | |
shell.publish((self.on_change)(Action::Undo)); | |
return event::Status::Captured; | |
} | |
(Key::Character("y"), true, false) | (Key::Character("z"), true, true) => { | |
shell.publish((self.on_change)(Action::Redo)); | |
return event::Status::Captured; | |
} | |
_ => (), | |
}; | |
} | |
} | |
self.content.as_widget_mut().on_event( | |
&mut tree.children[0], | |
event, | |
layout, | |
cursor, | |
renderer, | |
clipboard, | |
shell, | |
viewport, | |
) | |
} | |
fn mouse_interaction( | |
&self, | |
tree: &Tree, | |
layout: Layout<'_>, | |
cursor_position: mouse::Cursor, | |
viewport: &Rectangle, | |
renderer: &Renderer, | |
) -> mouse::Interaction { | |
self.content | |
.as_widget() | |
.mouse_interaction(&tree.children[0], layout, cursor_position, viewport, renderer) | |
} | |
fn draw( | |
&self, | |
tree: &Tree, | |
renderer: &mut Renderer, | |
theme: &Theme, | |
style: &renderer::Style, | |
layout: Layout<'_>, | |
cursor: mouse::Cursor, | |
viewport: &Rectangle, | |
) { | |
self.content | |
.as_widget() | |
.draw(&tree.children[0], renderer, theme, style, layout, cursor, viewport) | |
} | |
fn overlay<'b>( | |
&'b mut self, | |
tree: &'b mut Tree, | |
layout: Layout<'_>, | |
renderer: &Renderer, | |
translation: iced::Vector, | |
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { | |
self.content | |
.as_widget_mut() | |
.overlay(&mut tree.children[0], layout, renderer, translation) | |
} | |
} | |
impl<'a, Message, Theme, Renderer, F> From<Undoable<'a, Message, Theme, Renderer, F>> for Element<'a, Message, Theme, Renderer> | |
where | |
Message: 'a + Clone, | |
Theme: 'a, | |
Renderer: iced::advanced::text::Renderer + 'a, | |
F: Fn(Action) -> Message + 'a, | |
{ | |
fn from(undoable: Undoable<'a, Message, Theme, Renderer, F>) -> Self { | |
Self::new(undoable) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment