Add tool panel
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
use iced::advanced::layout::{self, Layout};
|
||||
use iced::advanced::{renderer, Clipboard};
|
||||
use iced::advanced::widget::{self, Widget};
|
||||
use iced::mouse;
|
||||
use iced::{Element, Event, Length, Rectangle, Size};
|
||||
|
||||
// We need the image renderer trait
|
||||
use iced::advanced::image as img;
|
||||
// ---------- State ----------
|
||||
|
||||
/// Internal state tracking whether the button is currently pressed.
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
is_pressed: bool,
|
||||
}
|
||||
|
||||
// ---------- Widget struct ----------
|
||||
|
||||
/// A button that displays one image when idle and another when pressed.
|
||||
pub struct ImageButton<Handle, Message> {
|
||||
normal: Handle,
|
||||
pressed: Handle,
|
||||
width: Length,
|
||||
height: Length,
|
||||
on_press: Option<Message>,
|
||||
is_pressed: bool,
|
||||
}
|
||||
|
||||
pub fn image_button<Handle, Message>(normal: impl Into<Handle>, pressed: impl Into<Handle>, is_pressed: bool) -> ImageButton<Handle, Message> {
|
||||
ImageButton::new(normal, pressed, is_pressed)
|
||||
}
|
||||
|
||||
impl<Handle, Message> ImageButton<Handle, Message> {
|
||||
/// Create a new [`ImageButton`].
|
||||
///
|
||||
/// * `normal` – image shown in the default / hover state
|
||||
/// * `pressed` – image shown while the left mouse button is held
|
||||
pub fn new(normal: impl Into<Handle>, pressed: impl Into<Handle>, is_pressed: bool) -> Self {
|
||||
Self {
|
||||
normal: normal.into(),
|
||||
pressed: pressed.into(),
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
on_press: None,
|
||||
is_pressed,
|
||||
}
|
||||
}
|
||||
|
||||
/// The message to emit when the button is clicked (press + release).
|
||||
pub fn on_press(mut self, message: Message) -> Self {
|
||||
self.on_press = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the widget width.
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the widget height.
|
||||
pub fn height(mut self, height: impl Into<Length>) -> Self {
|
||||
self.height = height.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Widget impl ----------
|
||||
|
||||
impl<Message, Theme, Renderer, Handle> Widget<Message, Theme, Renderer>
|
||||
for ImageButton<Handle, Message>
|
||||
where
|
||||
Renderer: img::Renderer<Handle = Handle>,
|
||||
Handle: Clone,
|
||||
Message: Clone,
|
||||
{
|
||||
// --- Tree (internal state) ---
|
||||
|
||||
fn tag(&self) -> widget::tree::Tag {
|
||||
widget::tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> widget::tree::State {
|
||||
widget::tree::State::new(State::default())
|
||||
}
|
||||
|
||||
// --- Size ---
|
||||
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
// --- Layout ---
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_tree: &mut widget::Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
// Use the natural dimensions of the normal image if the renderer
|
||||
// can measure it; otherwise fall back to the limits maximum.
|
||||
let size = renderer
|
||||
.measure_image(&self.normal)
|
||||
.map(|s| Size::new(s.width as f32, s.height as f32))
|
||||
.unwrap_or_else(|| limits.max());
|
||||
|
||||
let size = limits.resolve(self.width, self.height, size);
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
// --- Events ---
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut widget::Tree,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut iced::advanced::Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let bounds = layout.bounds();
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||
if cursor.is_over(bounds) {
|
||||
shell.capture_event();
|
||||
shell.request_redraw();
|
||||
if let Some(on_press) = self.on_press.clone() {
|
||||
shell.publish(on_press);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Cursor ---
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &widget::Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
if cursor.is_over(layout.bounds()) && self.on_press.is_some() {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::None
|
||||
}
|
||||
}
|
||||
|
||||
// --- Draw ---
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &widget::Tree,
|
||||
renderer: &mut Renderer,
|
||||
_theme: &Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
_cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
let bounds = layout.bounds();
|
||||
|
||||
// Pick the correct image handle.
|
||||
let handle = if self.is_pressed {
|
||||
&self.pressed
|
||||
} else {
|
||||
&self.normal
|
||||
};
|
||||
|
||||
renderer.draw_image(
|
||||
img::Image {
|
||||
handle: handle.clone(),
|
||||
border_radius: 0.0.into(),
|
||||
filter_method: img::FilterMethod::Linear,
|
||||
rotation: iced::Radians(0.0),
|
||||
opacity: 1.0,
|
||||
snap: false,
|
||||
},
|
||||
bounds, // drawing bounds
|
||||
bounds, // clip bounds
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Into<Element> ----------
|
||||
|
||||
impl<'a, Message, Theme, Renderer, Handle> From<ImageButton<Handle, Message>>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: img::Renderer<Handle = Handle> + 'a,
|
||||
Handle: Clone + 'a,
|
||||
Message: Clone + 'a,
|
||||
Theme: 'a,
|
||||
{
|
||||
fn from(widget: ImageButton<Handle, Message>) -> Self {
|
||||
Self::new(widget)
|
||||
}
|
||||
}
|
||||
169
src/main.rs
169
src/main.rs
@@ -1,41 +1,154 @@
|
||||
|
||||
|
||||
use iced::widget::{Column, Row, button, column, text, Grid, row};
|
||||
use iced::{Border, Color, Task};
|
||||
use iced::padding;
|
||||
use iced::widget::container;
|
||||
use iced::Theme;
|
||||
|
||||
mod image_button;
|
||||
|
||||
use iced::widget::{Column, button, column, text};
|
||||
#[derive(Default)]
|
||||
struct Counter {
|
||||
value: i32,
|
||||
struct Paint {
|
||||
tool_pressed: [bool; Tool::Count as usize],
|
||||
}
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Message {
|
||||
Increment,
|
||||
Decrement,
|
||||
|
||||
FreeFormSelectClicked,
|
||||
SelectClicked,
|
||||
EraserClicked,
|
||||
FillWithColorClicked,
|
||||
PickColorClicked,
|
||||
MagnifierClicked,
|
||||
PencilClicked,
|
||||
BrushClicked,
|
||||
AirbrushClicked,
|
||||
TextClicked,
|
||||
LineClicked,
|
||||
CurveClicked,
|
||||
RectangleClicked,
|
||||
PolygonClicked,
|
||||
EllipseClicked,
|
||||
RoundedRectangleClicked,
|
||||
}
|
||||
impl Counter {
|
||||
pub fn view(&self) -> Column<Message> {
|
||||
// We use a column: a simple vertical layout
|
||||
column![
|
||||
// The increment button. We tell it to produce an
|
||||
// `Increment` message when pressed
|
||||
button("+").on_press(Message::Increment),
|
||||
// We show the value of the counter here
|
||||
text(self.value).size(50),
|
||||
// The decrement button. We tell it to produce a
|
||||
// `Decrement` message when pressed
|
||||
button("-").on_press(Message::Decrement),
|
||||
]
|
||||
}
|
||||
pub fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Increment => {
|
||||
self.value += 1;
|
||||
}
|
||||
Message::Decrement => {
|
||||
self.value -= 1;
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn to_tool_index(&self) -> usize {
|
||||
match self {
|
||||
Message::FreeFormSelectClicked => 0,
|
||||
Message::SelectClicked => 1,
|
||||
Message::EraserClicked => 2,
|
||||
Message::FillWithColorClicked => 3,
|
||||
Message::PickColorClicked => 4,
|
||||
Message::MagnifierClicked => 5,
|
||||
Message::PencilClicked => 6,
|
||||
Message::BrushClicked => 7,
|
||||
Message::AirbrushClicked => 8,
|
||||
Message::TextClicked => 9,
|
||||
Message::LineClicked => 10,
|
||||
Message::CurveClicked => 11,
|
||||
Message::RectangleClicked => 12,
|
||||
Message::PolygonClicked => 13,
|
||||
Message::EllipseClicked => 14,
|
||||
Message::RoundedRectangleClicked => 15,
|
||||
_ => usize::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> iced::Result {
|
||||
iced::run(Counter::update, Counter::view)
|
||||
pub enum Tool {
|
||||
FreeFormSelect,
|
||||
Select,
|
||||
Eraser,
|
||||
FillWithColor,
|
||||
PickColor,
|
||||
Magnifier,
|
||||
Pencil,
|
||||
Brush,
|
||||
Airbrush,
|
||||
Text,
|
||||
Line,
|
||||
Curve,
|
||||
Rectangle,
|
||||
Polygon,
|
||||
Ellipse,
|
||||
RoundedRectangle,
|
||||
|
||||
Count,
|
||||
}
|
||||
|
||||
impl Paint {
|
||||
pub fn view(&self) -> Column<'_, Message> {
|
||||
let mut grid = Grid::new();
|
||||
grid = grid.columns(2).width(100);
|
||||
|
||||
for i in 0..(Tool::Count as usize) {
|
||||
let msg = match i {
|
||||
0 => Message::FreeFormSelectClicked,
|
||||
1 => Message::SelectClicked,
|
||||
2 => Message::EraserClicked,
|
||||
3 => Message::FillWithColorClicked,
|
||||
4 => Message::PickColorClicked,
|
||||
5 => Message::MagnifierClicked,
|
||||
6 => Message::PencilClicked,
|
||||
7 => Message::BrushClicked,
|
||||
8 => Message::AirbrushClicked,
|
||||
9 => Message::TextClicked,
|
||||
10 => Message::LineClicked,
|
||||
11 => Message::CurveClicked,
|
||||
12 => Message::RectangleClicked,
|
||||
13 => Message::PolygonClicked,
|
||||
14 => Message::EllipseClicked,
|
||||
15 => Message::RoundedRectangleClicked,
|
||||
_ => Message::FreeFormSelectClicked,
|
||||
};
|
||||
let btn = image_button::image_button(
|
||||
format!("image/normal/normal_{:02}.jpg", i + 1),
|
||||
format!("image/selected/selected_{:02}.jpg", i + 1),
|
||||
self.tool_pressed[i],
|
||||
).on_press(msg);
|
||||
grid = grid.push(btn);
|
||||
}
|
||||
|
||||
// We use a column: a simple vertical layout
|
||||
column![
|
||||
button("-").on_press(Message::Decrement),
|
||||
|
||||
container(grid).padding(padding::left(5).right(5).bottom(100)).style(|theme: &Theme| {
|
||||
let palette = theme.extended_palette();
|
||||
|
||||
container::Style {
|
||||
background: Some(Color::from_rgb8(192, 192, 192).into()),
|
||||
text_color: Some(palette.background.weakest.text),
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
radius: 5.0.into(),
|
||||
color: palette.background.weak.color,
|
||||
},
|
||||
..container::Style::default()
|
||||
}
|
||||
}),
|
||||
]
|
||||
}
|
||||
pub fn update(&mut self, message: Message) {
|
||||
self.press_tool(&message);
|
||||
}
|
||||
|
||||
pub fn press_tool(&mut self, message: &Message) {
|
||||
let idx = message.to_tool_index();
|
||||
if idx == usize::MAX {
|
||||
return;
|
||||
}
|
||||
let old_value = self.tool_pressed[idx];
|
||||
for i in 0..(Tool::Count as usize) {
|
||||
self.tool_pressed[i] = false;
|
||||
}
|
||||
self.tool_pressed[idx] = !old_value;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> iced::Result {
|
||||
iced::run(Paint::update, Paint::view)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user