Skip to main content

blockworx/tools/
icon.rs

1use std::time::Duration;
2
3use crate::theme::Style;
4use blockworx_doc::block_model::Asset;
5use blockworx_doc::id::BlockId;
6use blockworx_paint::{Canvas, Cursor, Event, Interaction};
7use std::sync::mpsc::{Receiver, TryRecvError};
8
9use crate::{
10    shape::ShapeId,
11    tools::{
12        names::ToolName,
13        new_image::spawn_image_dialog,
14        resize_block::ResizeBlock,
15        tool::{Action, ToolTrait},
16    },
17    widget::drawing::Drawing,
18};
19
20/// Attach an icon to a block. Usable two ways: with a block already selected the
21/// toolbar button (or the selection overlay's icon button) jumps straight to the
22/// image dialog for that block; otherwise a finger cursor lets the user click a
23/// block to attach to. The picked image becomes the block's foreground
24/// [`icon`](blockworx_doc::block_model::Block::icon), left selected for
25/// repositioning.
26pub enum IconTool {
27    /// Pick a block to attach an icon to (finger cursor).
28    Idle,
29    /// A block was chosen up front (it was already selected); open the dialog for
30    /// it on the next frame, once a painter is available to spawn it.
31    Armed(BlockId),
32    /// Waiting on the off-thread file dialog for `block`.
33    Pending {
34        rx: Receiver<Option<Asset>>,
35        block: BlockId,
36    },
37}
38
39impl ToolTrait for IconTool {
40    fn name(&self) -> ToolName {
41        ToolName::Icon
42    }
43
44    fn widget<C: Canvas>(
45        &mut self,
46        data: &mut Drawing,
47        interaction: &Interaction,
48        painter: &mut Style<'_, C>,
49    ) -> Option<Action> {
50        crate::widget::display::widget(data, interaction, painter);
51        match self {
52            IconTool::Idle => {
53                painter.set_cursor(Cursor::PointingHand);
54                if let Some(Event::Clicked { pos }) = interaction.event
55                    && let Some(ShapeId::Rect(rid)) = data.shape_at_pos(pos)
56                {
57                    *self = IconTool::Pending {
58                        rx: spawn_image_dialog(painter.waker()),
59                        block: rid,
60                    };
61                }
62            }
63            IconTool::Armed(block) => {
64                let block = *block;
65                *self = IconTool::Pending {
66                    rx: spawn_image_dialog(painter.waker()),
67                    block,
68                };
69            }
70            IconTool::Pending { rx, block } => {
71                let block = *block;
72                match rx.try_recv() {
73                    Ok(picked) => {
74                        // Select the icon on success so it can be repositioned; on
75                        // cancel fall back to selecting the block.
76                        // A refused file — cancelled, or over the asset limit —
77                        // leaves the block selected, the same as picking nothing.
78                        let shape = match picked.as_ref().filter(|asset| {
79                            crate::edit::lower::asset_within_limit(asset, "the picked icon")
80                        }) {
81                            Some(asset) => {
82                                data.set_icon(block, asset);
83                                ShapeId::Icon(block)
84                            }
85                            None => ShapeId::Rect(block),
86                        };
87                        *self = IconTool::Idle;
88                        return Some(Action::SwitchTool(ResizeBlock::Selected { shape }.into()));
89                    }
90                    Err(TryRecvError::Empty) => {
91                        painter.request_repaint_after(Duration::from_millis(100));
92                    }
93                    Err(TryRecvError::Disconnected) => *self = IconTool::Idle,
94                }
95            }
96        }
97        None
98    }
99}