Skip to content

Instantly share code, notes, and snippets.

@mtkennerly
Last active March 10, 2024 10:39
Show Gist options
  • Save mtkennerly/edea51b6c35b1cb85421107070cc7569 to your computer and use it in GitHub Desktop.
Save mtkennerly/edea51b6c35b1cb85421107070cc7569 to your computer and use it in GitHub Desktop.
Iced 0.12.1 issue with Undoable widget
[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"] }
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()
}
}
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