URI: 
       Improved pipette tool. - icy_draw - icy_draw is the successor to mystic draw. fork / mirror
  HTML git clone https://git.drkhsh.at/icy_draw.git
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 330e370a28f62bbbd4997283e8f3a5d9542332b4
   DIR parent e164696a70f622118b1897056a7c311f57c05714
  HTML Author: Mike Krüger <mkrueger@posteo.de>
       Date:   Mon, 18 Sep 2023 19:15:36 +0200
       
       Improved pipette tool.
       
       Mid click selects it now.
       
       Fixes issue #25
       
       Diffstat:
         M src/model/tools/brush_imp.rs        |       4 ++--
         M src/model/tools/click_imp.rs        |       4 ++--
         M src/model/tools/erase_imp.rs        |       4 ++--
         M src/model/tools/fill_imp.rs         |       8 ++++----
         M src/model/tools/flip_imp.rs         |       6 +++---
         M src/model/tools/font_imp.rs         |       4 ++--
         M src/model/tools/line_imp.rs         |       4 ++--
         M src/model/tools/mod.rs              |       4 ++--
         M src/model/tools/pencil_imp.rs       |       4 ++--
         M src/model/tools/pipette_imp.rs      |     130 +++++++++++++++++++++++++------
         M src/model/tools/select_imp.rs       |       4 ++--
         M src/ui/document_docking.rs          |      20 +++++++++++++++++++-
         M src/ui/editor/ansi/mod.rs           |       8 ++++++--
         M src/ui/main_window.rs               |      31 ++++++++++++++++++++++---------
         M src/ui/messages.rs                  |       9 +++++++--
         M src/ui/tool_switcher.rs             |      20 +++++++++++++-------
       
       16 files changed, 198 insertions(+), 66 deletions(-)
       ---
   DIR diff --git a/src/model/tools/brush_imp.rs b/src/model/tools/brush_imp.rs
       @@ -263,14 +263,14 @@ impl Tool for BrushTool {
                pos: Position,
                _pos_abs: Position,
                _response: &Response,
       -    ) -> super::Event {
       +    ) -> Option<Message> {
                if button == 1 {
                    let _op: AtomicUndoGuard =
                        editor.begin_atomic_undo(fl!(crate::LANGUAGE_LOADER, "undo-paint-brush"));
        
                    self.paint_brush(editor, pos);
                }
       -        super::Event::None
       +        None
            }
        
            fn handle_drag(
   DIR diff --git a/src/model/tools/click_imp.rs b/src/model/tools/click_imp.rs
       @@ -56,12 +56,12 @@ impl Tool for ClickTool {
                pos: Position,
                cur_abs: Position,
                _response: &egui::Response,
       -    ) -> Event {
       +    ) -> Option<Message> {
                if button == 1 && !is_inside_selection(editor, cur_abs) {
                    editor.set_caret_position(pos);
                    editor.buffer_view.lock().clear_selection();
                }
       -        Event::None
       +        None
            }
        
            fn handle_drag_begin(&mut self, editor: &mut AnsiEditor, _response: &egui::Response) -> Event {
   DIR diff --git a/src/model/tools/erase_imp.rs b/src/model/tools/erase_imp.rs
       @@ -161,13 +161,13 @@ impl Tool for EraseTool {
                pos: Position,
                _pos_abs: Position,
                _response: &egui::Response,
       -    ) -> super::Event {
       +    ) -> Option<Message> {
                if button == 1 {
                    let _undo = editor.begin_atomic_undo(fl!(crate::LANGUAGE_LOADER, "undo-eraser"));
        
                    self.eraser(editor, pos);
                }
       -        super::Event::None
       +        None
            }
        
            fn handle_drag(
   DIR diff --git a/src/model/tools/fill_imp.rs b/src/model/tools/fill_imp.rs
       @@ -6,7 +6,7 @@ use icy_engine::{AttributedChar, Size, TextPane};
        
        use crate::{AnsiEditor, Message};
        
       -use super::{brush_imp::draw_glyph, Event, Position, Tool};
       +use super::{brush_imp::draw_glyph, Position, Tool};
        
        #[derive(Clone, Copy, PartialEq, Eq)]
        pub enum FillType {
       @@ -246,10 +246,10 @@ impl Tool for FillTool {
                pos: Position,
                _pos_abs: Position,
                _response: &egui::Response,
       -    ) -> Event {
       +    ) -> Option<Message> {
                if button == 1 {
                    if editor.get_cur_layer_index() >= editor.buffer_view.lock().get_buffer().layers.len() {
       -                return Event::None;
       +                return None;
                    }
                    let attr = editor.buffer_view.lock().get_caret().get_attribute();
                    let ch = editor
       @@ -271,6 +271,6 @@ impl Tool for FillTool {
                        op.fill(editor, pos);
                    }
                }
       -        Event::None
       +        None
            }
        }
   DIR diff --git a/src/model/tools/flip_imp.rs b/src/model/tools/flip_imp.rs
       @@ -2,7 +2,7 @@ use eframe::egui;
        
        use crate::{AnsiEditor, Message};
        
       -use super::{Event, Position, Tool};
       +use super::{Position, Tool};
        pub struct FlipTool {}
        
        impl Tool for FlipTool {
       @@ -41,7 +41,7 @@ impl Tool for FlipTool {
                pos: Position,
                _pos_abs: Position,
                _response: &egui::Response,
       -    ) -> Event {
       +    ) -> Option<Message> {
                if button == 1 {
                    let mut ch = editor.get_char(pos);
        
       @@ -55,6 +55,6 @@ impl Tool for FlipTool {
        
                    editor.set_char(pos, ch);
                }
       -        Event::None
       +        None
            }
        } //   [176, 177, 178, 219, 223, 220, 221, 222, 254, 250 ],
   DIR diff --git a/src/model/tools/font_imp.rs b/src/model/tools/font_imp.rs
       @@ -324,12 +324,12 @@ impl Tool for FontTool {
                pos: Position,
                _pos_abs: Position,
                _response: &egui::Response,
       -    ) -> Event {
       +    ) -> Option<Message> {
                if button == 1 {
                    editor.set_caret_position(pos);
                    editor.buffer_view.lock().clear_selection();
                }
       -        Event::None
       +        None
            }
        
            fn handle_hover(
   DIR diff --git a/src/model/tools/line_imp.rs b/src/model/tools/line_imp.rs
       @@ -235,11 +235,11 @@ impl Tool for LineTool {
                pos: Position,
                _pos_abs: Position,
                _response: &egui::Response,
       -    ) -> Event {
       +    ) -> Option<Message> {
                if button == 1 {
                    editor.set_caret_position(pos);
                }
       -        Event::None
       +        None
            }
        
            fn handle_hover(
   DIR diff --git a/src/model/tools/mod.rs b/src/model/tools/mod.rs
       @@ -125,8 +125,8 @@ pub trait Tool {
                _pos: Position,
                _pos_abs: Position,
                _response: &Response,
       -    ) -> Event {
       -        Event::None
       +    ) -> Option<Message> {
       +        None
            }
        
            fn handle_drag_begin(&mut self, _editor: &mut AnsiEditor, _response: &egui::Response) -> Event {
   DIR diff --git a/src/model/tools/pencil_imp.rs b/src/model/tools/pencil_imp.rs
       @@ -199,7 +199,7 @@ impl Tool for PencilTool {
                pos: Position,
                _pos_abs: Position,
                _response: &egui::Response,
       -    ) -> super::Event {
       +    ) -> Option<Message> {
                if button == 1 {
                    self.last_pos = pos;
                    let _op: AtomicUndoGuard =
       @@ -208,7 +208,7 @@ impl Tool for PencilTool {
                    self.paint_brush(editor, pos);
                    editor.join_overlay(fl!(crate::LANGUAGE_LOADER, "undo-pencil"));
                }
       -        super::Event::None
       +        None
            }
            fn handle_hover(
                &mut self,
   DIR diff --git a/src/model/tools/pipette_imp.rs b/src/model/tools/pipette_imp.rs
       @@ -1,13 +1,20 @@
       -use eframe::egui;
       +use eframe::{
       +    egui::{self, TextureOptions},
       +    emath::Align2,
       +    epaint::{Color32, FontId, Rounding, Vec2},
       +};
       +use egui_extras::RetainedImage;
        use i18n_embed_fl::fl;
       -use icy_engine::AttributedChar;
       +use icy_engine::{AttributedChar, Buffer, TextAttribute};
        
       -use crate::{AnsiEditor, Message};
       +use crate::{create_retained_image, AnsiEditor, Message};
        
       -use super::{Event, Position, Tool};
       +use super::{Position, Tool};
        #[derive(Default)]
        pub struct PipetteTool {
            cur_char: Option<AttributedChar>,
       +    ch: Option<char>,
       +    char_image: Option<RetainedImage>,
        }
        
        impl Tool for PipetteTool {
       @@ -27,24 +34,74 @@ impl Tool for PipetteTool {
                &mut self,
                _ctx: &egui::Context,
                ui: &mut egui::Ui,
       -        _editor_opt: Option<&AnsiEditor>,
       +        editor_opt: Option<&AnsiEditor>,
            ) -> Option<Message> {
       +        let Some(editor) = editor_opt else {
       +            return None;
       +        };
       +
                if let Some(ch) = self.cur_char {
       -            ui.label(fl!(
       -                crate::LANGUAGE_LOADER,
       -                "pipette_tool_char_code",
       -                code = (ch.ch as u32)
       -            ));
       -            ui.label(fl!(
       -                crate::LANGUAGE_LOADER,
       -                "pipette_tool_foreground",
       -                fg = ch.attribute.get_foreground()
       -            ));
       -            ui.label(fl!(
       -                crate::LANGUAGE_LOADER,
       -                "pipette_tool_background",
       -                bg = ch.attribute.get_background()
       -            ));
       +            ui.vertical_centered(|ui| {
       +                ui.label(fl!(
       +                    crate::LANGUAGE_LOADER,
       +                    "pipette_tool_char_code",
       +                    code = (ch.ch as u32)
       +                ));
       +
       +                if self.ch.is_none() || !self.ch.unwrap().eq(&ch.ch) {
       +                    self.ch = Some(ch.ch);
       +
       +                    let mut buf = Buffer::new((1, 1));
       +                    buf.clear_font_table();
       +                    buf.set_font(
       +                        0,
       +                        editor
       +                            .buffer_view
       +                            .lock()
       +                            .get_buffer()
       +                            .get_font(ch.get_font_page())
       +                            .unwrap()
       +                            .clone(),
       +                    );
       +                    buf.layers[0]
       +                        .set_char((0, 0), AttributedChar::new(ch.ch, TextAttribute::default()));
       +                    self.char_image =
       +                        Some(create_retained_image(&buf).with_options(TextureOptions::NEAREST));
       +                }
       +
       +                if let Some(image) = &self.char_image {
       +                    image.show_scaled(ui, 2.0);
       +                }
       +
       +                ui.label(fl!(
       +                    crate::LANGUAGE_LOADER,
       +                    "pipette_tool_foreground",
       +                    fg = ch.attribute.get_foreground()
       +                ));
       +                paint_color(
       +                    ui,
       +                    editor
       +                        .buffer_view
       +                        .lock()
       +                        .get_buffer()
       +                        .palette
       +                        .get_color(ch.attribute.get_foreground() as usize),
       +                );
       +                ui.label(fl!(
       +                    crate::LANGUAGE_LOADER,
       +                    "pipette_tool_background",
       +                    bg = ch.attribute.get_background()
       +                ));
       +                paint_color(
       +                    ui,
       +                    editor
       +                        .buffer_view
       +                        .lock()
       +                        .get_buffer()
       +                        .palette
       +                        .get_color(ch.attribute.get_background() as usize),
       +                );
       +            });
                }
                None
            }
       @@ -68,11 +125,40 @@ impl Tool for PipetteTool {
                pos: Position,
                _pos_abs: Position,
                _response: &egui::Response,
       -    ) -> Event {
       +    ) -> Option<Message> {
                if button == 1 {
                    let ch = editor.get_char(pos);
                    editor.set_caret_attribute(ch.attribute);
       +            return Some(Message::SelectPreviousTool);
                }
       -        Event::None
       +        None
            }
        }
       +
       +fn paint_color(ui: &mut egui::Ui, color: icy_engine::Color) {
       +    let (_, stroke_rect) = ui.allocate_space(Vec2::new(100.0, 32.0));
       +
       +    let painter = ui.painter_at(stroke_rect);
       +
       +    let (r, g, b) = color.get_rgb();
       +    painter.rect_filled(stroke_rect, Rounding::none(), Color32::BLACK);
       +    painter.rect_filled(stroke_rect.shrink(1.0), Rounding::none(), Color32::WHITE);
       +    let color = Color32::from_rgb(r, g, b);
       +    painter.rect_filled(stroke_rect.shrink(2.0), Rounding::none(), color);
       +
       +    let text_color = if (r as f32 * 0.299 + g as f32 * 0.587 + b as f32 * 0.114) > 186.0 {
       +        Color32::BLACK
       +    } else {
       +        Color32::WHITE
       +    };
       +
       +    let text = format!("#{r:02x}{g:02x}{b:02x}");
       +    let font_id: eframe::epaint::FontId = FontId::monospace(16.0);
       +    painter.text(
       +        stroke_rect.left_center() + Vec2::new(4., 0.),
       +        Align2::LEFT_CENTER,
       +        text,
       +        font_id,
       +        text_color,
       +    );
       +}
   DIR diff --git a/src/model/tools/select_imp.rs b/src/model/tools/select_imp.rs
       @@ -125,7 +125,7 @@ impl Tool for SelectTool {
                pos: Position,
                cur_abs: Position,
                response: &egui::Response,
       -    ) -> Event {
       +    ) -> Option<Message> {
                let cur_ch = editor.get_char_from_cur_layer(pos);
        
                let selection_mode = if response.ctx.input(|i| i.modifiers.shift_only()) {
       @@ -174,7 +174,7 @@ impl Tool for SelectTool {
                            )
                        }),
                }
       -        Event::None
       +        None
            }
        
            fn handle_drag_begin(&mut self, editor: &mut AnsiEditor, response: &egui::Response) -> Event {
   DIR diff --git a/src/ui/document_docking.rs b/src/ui/document_docking.rs
       @@ -112,7 +112,8 @@ impl DocumentTab {
        
        pub struct DocumentBehavior {
            pub tools: Arc<Mutex<Vec<Box<dyn Tool>>>>,
       -    pub selected_tool: usize,
       +    selected_tool: usize,
       +    prev_tool: usize,
            pub document_options: DocumentOptions,
        
            char_set_img: Option<RetainedImage>,
       @@ -134,6 +135,7 @@ impl DocumentBehavior {
                Self {
                    tools,
                    selected_tool: FIRST_TOOL,
       +            prev_tool: FIRST_TOOL,
                    document_options: DocumentOptions::default(),
                    char_set_img: None,
                    cur_char_set: usize::MAX,
       @@ -146,6 +148,22 @@ impl DocumentBehavior {
                    cur_selection: None,
                }
            }
       +
       +    pub fn get_selected_tool(&self) -> usize {
       +        self.selected_tool
       +    }
       +
       +    pub(crate) fn set_selected_tool(&mut self, tool: usize) {
       +        if self.selected_tool == tool {
       +            return;
       +        }
       +        self.prev_tool = self.selected_tool;
       +        self.selected_tool = tool;
       +    }
       +
       +    pub(crate) fn select_prev_tool(&mut self) {
       +        self.selected_tool = self.prev_tool;
       +    }
        }
        
        impl egui_tiles::Behavior<DocumentTab> for DocumentBehavior {
   DIR diff --git a/src/ui/editor/ansi/mod.rs b/src/ui/editor/ansi/mod.rs
       @@ -203,7 +203,7 @@ impl Document for AnsiEditor {
                let response = response.context_menu(|ui| {
                    message = terminal_context_menu(self, &options.commands, ui);
                });
       -        self.handle_response(ui, response, calc, cur_tool);
       +        self.handle_response(ui, response, calc, cur_tool, &mut message);
        
                message
            }
       @@ -525,6 +525,7 @@ impl AnsiEditor {
                mut response: Response,
                calc: TerminalCalc,
                cur_tool: &mut Box<dyn Tool>,
       +        message: &mut Option<Message>,
            ) -> Response {
                if response.has_focus() {
                    let events = ui.input(|i| i.events.clone());
       @@ -607,7 +608,10 @@ impl AnsiEditor {
                                             PointerButton::Extra1 => 4,
                                             PointerButton::Extra2 => 5,
                                         }; */
       -                    cur_tool.handle_click(self, 1, cp, cp_abs, &response);
       +                    let msg = cur_tool.handle_click(self, 1, cp, cp_abs, &response);
       +                    if message.is_none() {
       +                        *message = msg;
       +                    }
                            self.redraw_view();
                        }
                    }
   DIR diff --git a/src/ui/main_window.rs b/src/ui/main_window.rs
       @@ -14,7 +14,7 @@ use crate::{
            ToolTab, TopBar,
        };
        use directories::UserDirs;
       -use eframe::egui::Button;
       +use eframe::egui::{Button, PointerButton};
        use eframe::{
            egui::{self, Key, Response, SidePanel, Ui},
            epaint::FontId,
       @@ -52,6 +52,7 @@ pub struct MainWindow {
        pub const PASTE_TOOL: usize = 0;
        pub const FIRST_TOOL: usize = 1;
        pub const BRUSH_TOOL: usize = 3;
       +pub const PIPETTE_TOOL: usize = 6;
        
        impl MainWindow {
            pub fn create_id(&mut self) -> usize {
       @@ -639,7 +640,9 @@ impl eframe::App for MainWindow {
        
                        self.handle_message(msg);
        
       -                crate::add_tool_switcher(ctx, ui, self);
       +                let msg = crate::add_tool_switcher(ctx, ui, self);
       +                self.handle_message(msg);
       +
                        let mut tool_result = None;
                        if let Some(tool) = self
                            .document_behavior
       @@ -647,7 +650,7 @@ impl eframe::App for MainWindow {
                            .clone()
                            .lock()
                            .unwrap()
       -                    .get_mut(self.document_behavior.selected_tool)
       +                    .get_mut(self.document_behavior.get_selected_tool())
                        {
                            ui.horizontal(|ui| {
                                ui.add_space(4.0);
       @@ -701,13 +704,13 @@ impl eframe::App for MainWindow {
                                let last = lock.get_buffer().layers.len().saturating_sub(1);
                                if let Some(layer) = lock.get_buffer().layers.last() {
                                    if layer.role.is_paste()
       -                                && self.document_behavior.selected_tool != PASTE_TOOL
       +                                && self.document_behavior.get_selected_tool() != PASTE_TOOL
                                    {
                                        self.document_behavior.tools.lock().unwrap()[PASTE_TOOL] =
                                            Box::new(crate::model::paste_tool::PasteTool::new(
       -                                        self.document_behavior.selected_tool,
       +                                        self.document_behavior.get_selected_tool(),
                                            ));
       -                                self.document_behavior.selected_tool = PASTE_TOOL;
       +                                self.document_behavior.set_selected_tool(PASTE_TOOL);
        
                                        lock.get_edit_state_mut().set_current_layer(last);
                                    }
       @@ -803,9 +806,19 @@ impl eframe::App for MainWindow {
                        }
                    }
                    for evt in &i.events.clone() {
       -                if let eframe::egui::Event::Zoom(vec) = evt {
       -                    let scale = self.document_behavior.document_options.get_scale() * *vec;
       -                    self.document_behavior.document_options.set_scale(scale);
       +                match evt {
       +                    eframe::egui::Event::PointerButton {
       +                        button: PointerButton::Middle,
       +                        pressed: true,
       +                        ..
       +                    } => {
       +                        self.handle_message(Some(Message::SelectTool(PIPETTE_TOOL)));
       +                    }
       +                    eframe::egui::Event::Zoom(vec) => {
       +                        let scale = self.document_behavior.document_options.get_scale() * *vec;
       +                        self.document_behavior.document_options.set_scale(scale);
       +                    }
       +                    _ => (),
                        }
                    }
                });
   DIR diff --git a/src/ui/messages.rs b/src/ui/messages.rs
       @@ -140,6 +140,7 @@ pub enum Message {
            ToggleLineNumbers,
            RunPlugin(usize),
            OpenPluginDirectory,
       +    SelectPreviousTool,
        }
        
        pub const CTRL_SHIFT: egui::Modifiers = egui::Modifiers {
       @@ -486,7 +487,11 @@ impl MainWindow {
                    }
        
                    Message::SelectTool(tool) => {
       -                self.document_behavior.selected_tool = tool;
       +                self.document_behavior.set_selected_tool(tool);
       +            }
       +
       +            Message::SelectPreviousTool => {
       +                self.document_behavior.select_prev_tool();
                    }
        
                    Message::ShowAboutDialog => {
       @@ -589,7 +594,7 @@ impl MainWindow {
                            if let Some(layer) = Layer::from_clipboard_data(&data) {
                                unsafe {
                                    crate::model::brush_imp::CUSTOM_BRUSH = Some(layer);
       -                            self.document_behavior.selected_tool = crate::BRUSH_TOOL;
       +                            self.document_behavior.set_selected_tool(crate::BRUSH_TOOL);
                                }
                            }
                        }
   DIR diff --git a/src/ui/tool_switcher.rs b/src/ui/tool_switcher.rs
       @@ -1,16 +1,21 @@
       -use crate::MainWindow;
       +use crate::{MainWindow, Message};
        use eframe::{
            egui::{self, Sense},
            epaint::{pos2, Rect, Rounding, Vec2},
        };
        
       -pub fn add_tool_switcher(ctx: &egui::Context, ui: &mut egui::Ui, arg: &mut MainWindow) {
       +pub fn add_tool_switcher(
       +    ctx: &egui::Context,
       +    ui: &mut egui::Ui,
       +    arg: &MainWindow,
       +) -> Option<Message> {
       +    let mut msg = None;
            let spacing = 4.0;
            let icon_size = 28.0;
        
            if let Ok(tools) = arg.document_behavior.tools.lock() {
       -        if tools[arg.document_behavior.selected_tool].is_exclusive() {
       -            return;
       +        if tools[arg.document_behavior.get_selected_tool()].is_exclusive() {
       +            return msg;
                }
                let (id, back_rect) = ui.allocate_space(Vec2::new(200., 68.0));
                let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
       @@ -25,7 +30,7 @@ pub fn add_tool_switcher(ctx: &egui::Context, ui: &mut egui::Ui, arg: &mut MainW
        
                    let rect = Rect::from_min_size(pos.floor(), Vec2::new(icon_size, icon_size));
                    let response = ui.interact(rect, id.with(i), Sense::click());
       -            if i == arg.document_behavior.selected_tool {
       +            if i == arg.document_behavior.get_selected_tool() {
                        ui.painter().rect_filled(
                            rect.expand(2.0),
                            Rounding::same(4.0),
       @@ -52,7 +57,7 @@ pub fn add_tool_switcher(ctx: &egui::Context, ui: &mut egui::Ui, arg: &mut MainW
                    }
        
                    let painter = ui.painter_at(rect);
       -            let tint = if i == arg.document_behavior.selected_tool {
       +            let tint = if i == arg.document_behavior.get_selected_tool() {
                        ui.visuals().widgets.active.fg_stroke.color
                    } else {
                        ui.visuals().widgets.inactive.fg_stroke.color
       @@ -66,8 +71,9 @@ pub fn add_tool_switcher(ctx: &egui::Context, ui: &mut egui::Ui, arg: &mut MainW
                    }
        
                    if response.clicked() {
       -                arg.document_behavior.selected_tool = i;
       +                msg = Some(Message::SelectTool(i));
                    }
                }
            }
       +    msg
        }