Refactored/rewrote many drawing tools. - icy_draw - icy_draw is the successor to mystic draw. fork / mirror
 (HTM) git clone https://git.drkhsh.at/icy_draw.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 31bf8031530df4fe923228acb3547cc2f6ac8098
 (DIR) parent 5922ef2c275636716780b18b479e2a54fd891f15
 (HTM) Author: Mike Krüger <mkrueger@posteo.de>
       Date:   Sun, 24 Sep 2023 20:46:33 +0200
       
       Refactored/rewrote many drawing tools.
       
       The new engine is way easier to maintain/bugfix.
       
       Diffstat:
         M i18n/de/icy_draw.ftl                |       3 ++-
         M i18n/en/icy_draw.ftl                |       5 +++--
         M src/main.rs                         |       1 +
         M src/model/tools/draw_ellipse_fille… |     122 ++++++++-----------------------
         M src/model/tools/draw_ellipse_imp.rs |     138 +++++++------------------------
         M src/model/tools/draw_rectangle_fil… |     118 ++++++++-----------------------
         M src/model/tools/draw_rectangle_imp… |     137 +++++++------------------------
         M src/model/tools/line_imp.rs         |     463 ++-----------------------------
         M src/model/tools/mod.rs              |     164 +------------------------------
         M src/model/tools/pencil_imp.rs       |     206 ++++++-------------------------
         D src/model/tools/scan_lines.rs       |     411 ------------------------------
         A src/paint/ellipse.rs                |     106 ++++++++++++++++++++++++++++++
         A src/paint/half_block.rs             |     157 +++++++++++++++++++++++++++++++
         A src/paint/line.rs                   |      53 ++++++++++++++++++++++++++++++
         A src/paint/mod.rs                    |     203 +++++++++++++++++++++++++++++++
         A src/paint/rectangle.rs              |     102 +++++++++++++++++++++++++++++++
         M src/ui/editor/ansi/mod.rs           |       1 +
         M src/ui/main_window.rs               |      47 ++++---------------------------
       
       18 files changed, 818 insertions(+), 1619 deletions(-)
       ---
 (DIR) diff --git a/i18n/de/icy_draw.ftl b/i18n/de/icy_draw.ftl
       @@ -129,12 +129,13 @@ menu-open_plugin_directory=Erweiterungsverzeichnis öffnen…
        tool-fg=Fg
        tool-bg=Bg
        tool-solid=Solid
       -tool-line=Line
        tool-character=Zeichen
        tool-shade=Schattieren
        tool-colorize=Färben
        tool-size-label=Größe:
       +tool-full-block=Block
        tool-half-block=Halbblock
       +tool-outline=Outline
        tool-custom-brush=Benutzerdefinierter Pinsel
        
        tool-select-label=Auswahlmodus:
 (DIR) diff --git a/i18n/en/icy_draw.ftl b/i18n/en/icy_draw.ftl
       @@ -125,12 +125,13 @@ menu-open_plugin_directory=Open Plugin Directory…
        tool-fg=Fg
        tool-bg=Bg
        tool-solid=Solid
       -tool-line=Line
        tool-character=Character
        tool-shade=Shade
        tool-colorize=Colorize
        tool-size-label=Size:
       -tool-half-block=Half block
       +tool-full-block=Block
       +tool-half-block=Half Block
       +tool-outline=Outline
        tool-custom-brush=Custom brush
        
        tool-select-label=Selection mode:
 (DIR) diff --git a/src/main.rs b/src/main.rs
       @@ -6,6 +6,7 @@
        use eframe::egui;
        const VERSION: &str = env!("CARGO_PKG_VERSION");
        mod model;
       +mod paint;
        mod plugins;
        mod ui;
        mod util;
 (DIR) diff --git a/src/model/tools/draw_ellipse_filled_imp.rs b/src/model/tools/draw_ellipse_filled_imp.rs
       @@ -1,33 +1,27 @@
        use eframe::egui;
        use i18n_embed_fl::fl;
       -use icy_engine::{Rectangle, TextAttribute};
        use icy_engine_egui::TerminalCalc;
        
       -use crate::{AnsiEditor, Message};
       +use crate::{
       +    paint::{fill_ellipse, BrushMode, ColorMode},
       +    AnsiEditor, Message,
       +};
        
       -use super::{brush_imp::draw_glyph, plot_point, DrawMode, Plottable, Position, ScanLines, Tool};
       +use super::{Position, Tool};
        
        pub struct DrawEllipseFilledTool {
       -    pub draw_mode: DrawMode,
       -
       -    pub use_fore: bool,
       -    pub use_back: bool,
       -    pub attr: TextAttribute,
       -    pub char_code: std::rc::Rc<std::cell::RefCell<char>>,
       +    draw_mode: BrushMode,
       +    color_mode: ColorMode,
       +    char_code: std::rc::Rc<std::cell::RefCell<char>>,
        }
        
       -impl Plottable for DrawEllipseFilledTool {
       -    fn get_draw_mode(&self) -> DrawMode {
       -        self.draw_mode
       -    }
       -    fn get_use_fore(&self) -> bool {
       -        self.use_fore
       -    }
       -    fn get_use_back(&self) -> bool {
       -        self.use_back
       -    }
       -    fn get_char_code(&self) -> char {
       -        *self.char_code.borrow()
       +impl Default for DrawEllipseFilledTool {
       +    fn default() -> Self {
       +        Self {
       +            draw_mode: BrushMode::HalfBlock,
       +            color_mode: crate::paint::ColorMode::Both,
       +            char_code: std::rc::Rc::new(std::cell::RefCell::new('\u{00B0}')),
       +        }
            }
        }
        
       @@ -49,50 +43,10 @@ impl Tool for DrawEllipseFilledTool {
                ui: &mut egui::Ui,
                editor_opt: Option<&AnsiEditor>,
            ) -> Option<Message> {
       -        let mut result = None;
       -        ui.vertical_centered(|ui| {
       -            ui.horizontal(|ui| {
       -                if ui
       -                    .selectable_label(self.use_fore, fl!(crate::LANGUAGE_LOADER, "tool-fg"))
       -                    .clicked()
       -                {
       -                    self.use_fore = !self.use_fore;
       -                }
       -                if ui
       -                    .selectable_label(self.use_back, fl!(crate::LANGUAGE_LOADER, "tool-bg"))
       -                    .clicked()
       -                {
       -                    self.use_back = !self.use_back;
       -                }
       -            });
       -        });
       -
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Line,
       -            fl!(crate::LANGUAGE_LOADER, "tool-solid"),
       -        );
       -        ui.horizontal(|ui| {
       -            ui.radio_value(
       -                &mut self.draw_mode,
       -                DrawMode::Char,
       -                fl!(crate::LANGUAGE_LOADER, "tool-character"),
       -            );
       -            if let Some(editor) = editor_opt {
       -                result = draw_glyph(ui, editor, &self.char_code);
       -            }
       -        });
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Shade,
       -            fl!(crate::LANGUAGE_LOADER, "tool-shade"),
       -        );
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Colorize,
       -            fl!(crate::LANGUAGE_LOADER, "tool-colorize"),
       -        );
       -        result
       +        self.color_mode.show_ui(ui);
       +        self.draw_mode
       +            .show_ui(ui, editor_opt, self.char_code.clone());
       +        None
            }
        
            fn handle_hover(
       @@ -114,33 +68,17 @@ impl Tool for DrawEllipseFilledTool {
                _calc: &TerminalCalc,
            ) -> egui::Response {
                editor.clear_overlay_layer();
       -
       -        let mut lines = ScanLines::new(1);
       -
       -        if editor.drag_pos.start < editor.drag_pos.cur {
       -            lines.add_ellipse(Rectangle::from_pt(
       -                editor.drag_pos.start,
       -                editor.drag_pos.cur,
       -            ));
       -        } else {
       -            lines.add_ellipse(Rectangle::from_pt(
       -                editor.drag_pos.cur,
       -                editor.drag_pos.start,
       -            ));
       -        }
       -
       -        let draw = move |rect: Rectangle| {
       -            for y in 0..rect.size.height {
       -                for x in 0..rect.size.width {
       -                    plot_point(
       -                        editor,
       -                        self,
       -                        Position::new(rect.start.x + x, rect.start.y + y),
       -                    );
       -                }
       -            }
       -        };
       -        lines.fill(draw);
       +        let p1 = editor.drag_pos.start_half_block;
       +        let p2 = editor.half_block_click_pos;
       +        let start = Position::new(p1.x.min(p2.x), p1.y.min(p2.y));
       +        let end = Position::new(p1.x.max(p2.x), p1.y.max(p2.y));
       +        fill_ellipse(
       +            &mut editor.buffer_view.lock(),
       +            start,
       +            end,
       +            self.draw_mode.clone(),
       +            self.color_mode,
       +        );
                response
            }
        
 (DIR) diff --git a/src/model/tools/draw_ellipse_imp.rs b/src/model/tools/draw_ellipse_imp.rs
       @@ -1,36 +1,27 @@
        use eframe::egui;
        use i18n_embed_fl::fl;
       -use icy_engine::{Rectangle, TextAttribute};
        use icy_engine_egui::TerminalCalc;
        
       -use crate::{AnsiEditor, Message};
       -
       -use super::{
       -    brush_imp::draw_glyph, line_imp::set_half_block, plot_point, DrawMode, Plottable, Position,
       -    ScanLines, Tool,
       +use crate::{
       +    paint::{draw_ellipse, BrushMode, ColorMode},
       +    AnsiEditor, Message,
        };
        
       -pub struct DrawEllipseTool {
       -    pub draw_mode: DrawMode,
       +use super::{Position, Tool};
        
       -    pub use_fore: bool,
       -    pub use_back: bool,
       -    pub attr: TextAttribute,
       -    pub char_code: std::rc::Rc<std::cell::RefCell<char>>,
       +pub struct DrawEllipseTool {
       +    draw_mode: BrushMode,
       +    color_mode: ColorMode,
       +    char_code: std::rc::Rc<std::cell::RefCell<char>>,
        }
        
       -impl Plottable for DrawEllipseTool {
       -    fn get_draw_mode(&self) -> DrawMode {
       -        self.draw_mode
       -    }
       -    fn get_use_fore(&self) -> bool {
       -        self.use_fore
       -    }
       -    fn get_use_back(&self) -> bool {
       -        self.use_back
       -    }
       -    fn get_char_code(&self) -> char {
       -        *self.char_code.borrow()
       +impl Default for DrawEllipseTool {
       +    fn default() -> Self {
       +        Self {
       +            draw_mode: BrushMode::HalfBlock,
       +            color_mode: crate::paint::ColorMode::Both,
       +            char_code: std::rc::Rc::new(std::cell::RefCell::new('\u{00B0}')),
       +        }
            }
        }
        
       @@ -52,50 +43,10 @@ impl Tool for DrawEllipseTool {
                ui: &mut egui::Ui,
                editor_opt: Option<&AnsiEditor>,
            ) -> Option<Message> {
       -        let mut result = None;
       -        ui.vertical_centered(|ui| {
       -            ui.horizontal(|ui| {
       -                if ui
       -                    .selectable_label(self.use_fore, fl!(crate::LANGUAGE_LOADER, "tool-fg"))
       -                    .clicked()
       -                {
       -                    self.use_fore = !self.use_fore;
       -                }
       -                if ui
       -                    .selectable_label(self.use_back, fl!(crate::LANGUAGE_LOADER, "tool-bg"))
       -                    .clicked()
       -                {
       -                    self.use_back = !self.use_back;
       -                }
       -            });
       -        });
       -
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Line,
       -            fl!(crate::LANGUAGE_LOADER, "tool-line"),
       -        );
       -        ui.horizontal(|ui| {
       -            ui.radio_value(
       -                &mut self.draw_mode,
       -                DrawMode::Char,
       -                fl!(crate::LANGUAGE_LOADER, "tool-character"),
       -            );
       -            if let Some(editor) = editor_opt {
       -                result = draw_glyph(ui, editor, &self.char_code);
       -            }
       -        });
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Shade,
       -            fl!(crate::LANGUAGE_LOADER, "tool-shade"),
       -        );
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Colorize,
       -            fl!(crate::LANGUAGE_LOADER, "tool-colorize"),
       -        );
       -        result
       +        self.color_mode.show_ui(ui);
       +        self.draw_mode
       +            .show_ui(ui, editor_opt, self.char_code.clone());
       +        None
            }
        
            fn handle_hover(
       @@ -117,46 +68,17 @@ impl Tool for DrawEllipseTool {
                _calc: &TerminalCalc,
            ) -> egui::Response {
                editor.clear_overlay_layer();
       -
       -        let mut lines = ScanLines::new(1);
       -        let mut start = editor.drag_pos.start;
       -        let mut cur = editor.drag_pos.cur;
       -
       -        if self.draw_mode == DrawMode::Line {
       -            start = editor.drag_pos.start_half_block;
       -            cur = editor.half_block_click_pos;
       -        }
       -
       -        if start < cur {
       -            lines.add_ellipse(Rectangle::from_pt(start, cur));
       -        } else {
       -            lines.add_ellipse(Rectangle::from_pt(cur, start));
       -        }
       -        println!("{start} -> {cur}");
       -        let col = editor
       -            .buffer_view
       -            .lock()
       -            .get_caret()
       -            .get_attribute()
       -            .get_foreground();
       -        for rect in lines.outline() {
       -            for y in 0..rect.size.height {
       -                for x in 0..rect.size.width {
       -                    let pos = Position::new(rect.start.x + x, rect.start.y + y);
       -                    match self.draw_mode {
       -                        DrawMode::Line => {
       -                            set_half_block(editor, pos, col);
       -                        }
       -                        DrawMode::Char
       -                        | DrawMode::Shade
       -                        | DrawMode::Colorize
       -                        /*| DrawMode::Outline*/ => {
       -                            plot_point(editor, self, pos);
       -                        }
       -                    }
       -                }
       -            }
       -        }
       +        let p1 = editor.drag_pos.start_half_block;
       +        let p2 = editor.half_block_click_pos;
       +        let start = Position::new(p1.x.min(p2.x), p1.y.min(p2.y));
       +        let end = Position::new(p1.x.max(p2.x), p1.y.max(p2.y));
       +        draw_ellipse(
       +            &mut editor.buffer_view.lock(),
       +            start,
       +            end,
       +            self.draw_mode.clone(),
       +            self.color_mode,
       +        );
                response
            }
        
 (DIR) diff --git a/src/model/tools/draw_rectangle_filled_imp.rs b/src/model/tools/draw_rectangle_filled_imp.rs
       @@ -1,34 +1,28 @@
        use eframe::egui;
        use i18n_embed_fl::fl;
       -use icy_engine::{Position, Rectangle, TextAttribute};
       +use icy_engine::Position;
        use icy_engine_egui::TerminalCalc;
        
       -use crate::{AnsiEditor, Message};
       +use crate::{
       +    paint::{fill_rectangle, BrushMode, ColorMode},
       +    AnsiEditor, Message,
       +};
        
       -use super::{brush_imp::draw_glyph, plot_point, DrawMode, Plottable, ScanLines, Tool};
       +use super::Tool;
        
        pub struct DrawRectangleFilledTool {
       -    pub draw_mode: DrawMode,
       -
       -    pub use_fore: bool,
       -    pub use_back: bool,
       -    pub attr: TextAttribute,
       -    pub char_code: std::rc::Rc<std::cell::RefCell<char>>,
       +    draw_mode: BrushMode,
       +    color_mode: ColorMode,
       +    char_code: std::rc::Rc<std::cell::RefCell<char>>,
        }
        
       -impl Plottable for DrawRectangleFilledTool {
       -    fn get_draw_mode(&self) -> DrawMode {
       -        self.draw_mode
       -    }
       -
       -    fn get_use_fore(&self) -> bool {
       -        self.use_fore
       -    }
       -    fn get_use_back(&self) -> bool {
       -        self.use_back
       -    }
       -    fn get_char_code(&self) -> char {
       -        *self.char_code.borrow()
       +impl Default for DrawRectangleFilledTool {
       +    fn default() -> Self {
       +        Self {
       +            draw_mode: BrushMode::HalfBlock,
       +            color_mode: crate::paint::ColorMode::Both,
       +            char_code: std::rc::Rc::new(std::cell::RefCell::new('\u{00B0}')),
       +        }
            }
        }
        
       @@ -50,50 +44,10 @@ impl Tool for DrawRectangleFilledTool {
                ui: &mut egui::Ui,
                editor_opt: Option<&AnsiEditor>,
            ) -> Option<Message> {
       -        let mut result = None;
       -        ui.vertical_centered(|ui| {
       -            ui.horizontal(|ui| {
       -                if ui
       -                    .selectable_label(self.use_fore, fl!(crate::LANGUAGE_LOADER, "tool-fg"))
       -                    .clicked()
       -                {
       -                    self.use_fore = !self.use_fore;
       -                }
       -                if ui
       -                    .selectable_label(self.use_back, fl!(crate::LANGUAGE_LOADER, "tool-bg"))
       -                    .clicked()
       -                {
       -                    self.use_back = !self.use_back;
       -                }
       -            });
       -        });
       -
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Line,
       -            fl!(crate::LANGUAGE_LOADER, "tool-solid"),
       -        );
       -        ui.horizontal(|ui| {
       -            ui.radio_value(
       -                &mut self.draw_mode,
       -                DrawMode::Char,
       -                fl!(crate::LANGUAGE_LOADER, "tool-character"),
       -            );
       -            if let Some(editor) = editor_opt {
       -                result = draw_glyph(ui, editor, &self.char_code);
       -            }
       -        });
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Shade,
       -            fl!(crate::LANGUAGE_LOADER, "tool-shade"),
       -        );
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Colorize,
       -            fl!(crate::LANGUAGE_LOADER, "tool-colorize"),
       -        );
       -        result
       +        self.color_mode.show_ui(ui);
       +        self.draw_mode
       +            .show_ui(ui, editor_opt, self.char_code.clone());
       +        None
            }
        
            fn handle_hover(
       @@ -114,28 +68,18 @@ impl Tool for DrawRectangleFilledTool {
                editor: &mut AnsiEditor,
                _calc: &TerminalCalc,
            ) -> egui::Response {
       -        editor.buffer_view.lock().get_buffer_mut().remove_overlay();
                editor.clear_overlay_layer();
       -
       -        let mut lines = ScanLines::new(1);
       -        lines.add_rectangle(Rectangle::from_pt(
       -            editor.drag_pos.start,
       -            editor.drag_pos.cur,
       -        ));
       -
       -        let draw = move |rect: Rectangle| {
       -            for y in 0..rect.size.height {
       -                for x in 0..rect.size.width {
       -                    plot_point(
       -                        editor,
       -                        self,
       -                        Position::new(rect.start.x + x, rect.start.y + y),
       -                    );
       -                }
       -            }
       -        };
       -        lines.fill(draw);
       -
       +        let p1 = editor.drag_pos.start_half_block;
       +        let p2 = editor.half_block_click_pos;
       +        let start = Position::new(p1.x.min(p2.x), p1.y.min(p2.y));
       +        let end = Position::new(p1.x.max(p2.x), p1.y.max(p2.y));
       +        fill_rectangle(
       +            &mut editor.buffer_view.lock(),
       +            start,
       +            end,
       +            self.draw_mode.clone(),
       +            self.color_mode,
       +        );
                response
            }
        
 (DIR) diff --git a/src/model/tools/draw_rectangle_imp.rs b/src/model/tools/draw_rectangle_imp.rs
       @@ -1,37 +1,27 @@
        use eframe::egui;
        use i18n_embed_fl::fl;
       -use icy_engine::{Rectangle, TextAttribute};
        use icy_engine_egui::TerminalCalc;
        
       -use crate::{AnsiEditor, Message};
       -
       -use super::{
       -    brush_imp::draw_glyph, line_imp::set_half_block, plot_point, DrawMode, Plottable, Position,
       -    ScanLines, Tool,
       +use crate::{
       +    paint::{draw_rectangle, BrushMode, ColorMode},
       +    AnsiEditor, Message,
        };
        
       -pub struct DrawRectangleTool {
       -    pub draw_mode: DrawMode,
       +use super::{Position, Tool};
        
       -    pub use_fore: bool,
       -    pub use_back: bool,
       -    pub attr: TextAttribute,
       +pub struct DrawRectangleTool {
       +    draw_mode: BrushMode,
       +    color_mode: ColorMode,
            pub char_code: std::rc::Rc<std::cell::RefCell<char>>,
        }
        
       -impl Plottable for DrawRectangleTool {
       -    fn get_draw_mode(&self) -> DrawMode {
       -        self.draw_mode
       -    }
       -
       -    fn get_use_fore(&self) -> bool {
       -        self.use_fore
       -    }
       -    fn get_use_back(&self) -> bool {
       -        self.use_back
       -    }
       -    fn get_char_code(&self) -> char {
       -        *self.char_code.borrow()
       +impl Default for DrawRectangleTool {
       +    fn default() -> Self {
       +        Self {
       +            draw_mode: BrushMode::HalfBlock,
       +            color_mode: crate::paint::ColorMode::Both,
       +            char_code: std::rc::Rc::new(std::cell::RefCell::new('\u{00B0}')),
       +        }
            }
        }
        
       @@ -52,55 +42,10 @@ impl Tool for DrawRectangleTool {
                ui: &mut egui::Ui,
                editor_opt: Option<&AnsiEditor>,
            ) -> Option<Message> {
       -        let mut result = None;
       -        ui.vertical_centered(|ui| {
       -            ui.horizontal(|ui| {
       -                if ui
       -                    .selectable_label(self.use_fore, fl!(crate::LANGUAGE_LOADER, "tool-fg"))
       -                    .clicked()
       -                {
       -                    self.use_fore = !self.use_fore;
       -                }
       -                if ui
       -                    .selectable_label(self.use_back, fl!(crate::LANGUAGE_LOADER, "tool-bg"))
       -                    .clicked()
       -                {
       -                    self.use_back = !self.use_back;
       -                }
       -            });
       -        });
       -
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Line,
       -            fl!(crate::LANGUAGE_LOADER, "tool-line"),
       -        );
       -        ui.horizontal(|ui| {
       -            ui.radio_value(
       -                &mut self.draw_mode,
       -                DrawMode::Char,
       -                fl!(crate::LANGUAGE_LOADER, "tool-character"),
       -            );
       -            if let Some(editor) = editor_opt {
       -                result = draw_glyph(ui, editor, &self.char_code);
       -            }
       -        });
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Shade,
       -            fl!(crate::LANGUAGE_LOADER, "tool-shade"),
       -        );
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Colorize,
       -            fl!(crate::LANGUAGE_LOADER, "tool-colorize"),
       -        );
       -        /*   ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Outline,
       -            "Outline",
       -        );*/
       -        result
       +        self.color_mode.show_ui(ui);
       +        self.draw_mode
       +            .show_ui(ui, editor_opt, self.char_code.clone());
       +        None
            }
        
            fn handle_hover(
       @@ -122,41 +67,17 @@ impl Tool for DrawRectangleTool {
                _calc: &TerminalCalc,
            ) -> egui::Response {
                editor.clear_overlay_layer();
       -        let mut start = editor.drag_pos.start;
       -        let mut cur = editor.drag_pos.cur;
       -        if self.draw_mode == DrawMode::Line {
       -            start = editor.drag_pos.start_half_block;
       -            cur = editor.half_block_click_pos;
       -        }
       -
       -        let mut lines = ScanLines::new(1);
       -        lines.add_rectangle(Rectangle::from_pt(start, cur));
       -
       -        let col = editor
       -            .buffer_view
       -            .lock()
       -            .get_caret()
       -            .get_attribute()
       -            .get_foreground();
       -        for rect in lines.outline() {
       -            for y in 0..rect.size.height {
       -                for x in 0..rect.size.width {
       -                    let pos = Position::new(rect.start.x + x, rect.start.y + y);
       -                    match self.draw_mode {
       -                        DrawMode::Line => {
       -                            set_half_block(editor, pos, col);
       -                        }
       -                        DrawMode::Char
       -                        | DrawMode::Shade
       -                        | DrawMode::Colorize
       -                       /*  | DrawMode::Outline */=> {
       -                            plot_point(editor, self, pos);
       -                        }
       -                    }
       -                }
       -            }
       -        }
       -
       +        let p1 = editor.drag_pos.start_half_block;
       +        let p2 = editor.half_block_click_pos;
       +        let start = Position::new(p1.x.min(p2.x), p1.y.min(p2.y));
       +        let end = Position::new(p1.x.max(p2.x), p1.y.max(p2.y));
       +        draw_rectangle(
       +            &mut editor.buffer_view.lock(),
       +            start,
       +            end,
       +            self.draw_mode.clone(),
       +            self.color_mode,
       +        );
                response
            }
        
 (DIR) diff --git a/src/model/tools/line_imp.rs b/src/model/tools/line_imp.rs
       @@ -1,36 +1,31 @@
        use eframe::egui;
        use i18n_embed_fl::fl;
       -use icy_engine::{AttributedChar, Rectangle, TextAttribute, TextPane};
        use icy_engine_egui::TerminalCalc;
        
       -use crate::{model::ScanLines, AnsiEditor, Message};
       +use crate::{
       +    paint::{draw_line, BrushMode, ColorMode},
       +    AnsiEditor, Message,
       +};
        
       -use super::{brush_imp::draw_glyph, plot_point, DrawMode, Plottable, Position, Tool};
       +use super::{Position, Tool};
        
        pub struct LineTool {
       -    pub draw_mode: DrawMode,
       +    draw_mode: BrushMode,
       +    color_mode: ColorMode,
        
       -    pub use_fore: bool,
       -    pub use_back: bool,
       -    pub attr: TextAttribute,
            pub char_code: std::rc::Rc<std::cell::RefCell<char>>,
        
            pub old_pos: Position,
        }
        
       -impl Plottable for LineTool {
       -    fn get_draw_mode(&self) -> DrawMode {
       -        self.draw_mode
       -    }
       -
       -    fn get_use_fore(&self) -> bool {
       -        self.use_fore
       -    }
       -    fn get_use_back(&self) -> bool {
       -        self.use_back
       -    }
       -    fn get_char_code(&self) -> char {
       -        *self.char_code.borrow()
       +impl Default for LineTool {
       +    fn default() -> Self {
       +        Self {
       +            draw_mode: BrushMode::HalfBlock,
       +            color_mode: crate::paint::ColorMode::Both,
       +            char_code: std::rc::Rc::new(std::cell::RefCell::new('\u{00B0}')),
       +            old_pos: Position::default(),
       +        }
            }
        }
        
       @@ -182,50 +177,10 @@ impl Tool for LineTool {
                ui: &mut egui::Ui,
                editor_opt: Option<&AnsiEditor>,
            ) -> Option<Message> {
       -        let mut result = None;
       -        ui.vertical_centered(|ui| {
       -            ui.horizontal(|ui| {
       -                if ui
       -                    .selectable_label(self.use_fore, fl!(crate::LANGUAGE_LOADER, "tool-fg"))
       -                    .clicked()
       -                {
       -                    self.use_fore = !self.use_fore;
       -                }
       -                if ui
       -                    .selectable_label(self.use_back, fl!(crate::LANGUAGE_LOADER, "tool-bg"))
       -                    .clicked()
       -                {
       -                    self.use_back = !self.use_back;
       -                }
       -            });
       -        });
       -
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Line,
       -            fl!(crate::LANGUAGE_LOADER, "tool-line"),
       -        );
       -        ui.horizontal(|ui| {
       -            ui.radio_value(
       -                &mut self.draw_mode,
       -                DrawMode::Char,
       -                fl!(crate::LANGUAGE_LOADER, "tool-character"),
       -            );
       -            if let Some(editor) = editor_opt {
       -                result = draw_glyph(ui, editor, &self.char_code);
       -            }
       -        });
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Shade,
       -            fl!(crate::LANGUAGE_LOADER, "tool-shade"),
       -        );
       -        ui.radio_value(
       -            &mut self.draw_mode,
       -            DrawMode::Colorize,
       -            fl!(crate::LANGUAGE_LOADER, "tool-colorize"),
       -        );
       -        result
       +        self.color_mode.show_ui(ui);
       +        self.draw_mode
       +            .show_ui(ui, editor_opt, self.char_code.clone());
       +        None
            }
        
            fn handle_click(
       @@ -261,46 +216,13 @@ impl Tool for LineTool {
                _calc: &TerminalCalc,
            ) -> egui::Response {
                editor.clear_overlay_layer();
       -        let mut lines = ScanLines::new(1);
       -        if self.draw_mode == DrawMode::Line {
       -            lines.add_line(
       -                editor.drag_pos.start_half_block,
       -                editor.half_block_click_pos,
       -            );
       -            let col = editor
       -                .buffer_view
       -                .lock()
       -                .get_caret()
       -                .get_attribute()
       -                .get_foreground();
       -            let draw = move |rect: Rectangle| {
       -                for y in 0..rect.size.height {
       -                    for x in 0..rect.size.width {
       -                        set_half_block(
       -                            editor,
       -                            Position::new(rect.start.x + x, rect.start.y + y),
       -                            col,
       -                        );
       -                    }
       -                }
       -            };
       -            lines.fill(draw);
       -        } else {
       -            lines.add_line(editor.drag_pos.start, editor.drag_pos.cur);
       -            let draw = move |rect: Rectangle| {
       -                for y in 0..rect.size.height {
       -                    for x in 0..rect.size.width {
       -                        plot_point(
       -                            editor,
       -                            self,
       -                            Position::new(rect.start.x + x, rect.start.y + y),
       -                        );
       -                    }
       -                }
       -            };
       -            lines.fill(draw);
       -        }
       -
       +        draw_line(
       +            &mut editor.buffer_view.lock(),
       +            editor.drag_pos.start_half_block,
       +            editor.half_block_click_pos,
       +            self.draw_mode.clone(),
       +            self.color_mode,
       +        );
                response
            }
        
       @@ -313,336 +235,3 @@ impl Tool for LineTool {
                None
            }
        }
       -
       -fn get_half_block(
       -    editor: &AnsiEditor,
       -    pos: Position,
       -) -> (
       -    Position,
       -    i32,
       -    bool,
       -    bool,
       -    u32,
       -    u32,
       -    u32,
       -    u32,
       -    bool,
       -    u32,
       -    u32,
       -) {
       -    let text_y = pos.y / 2;
       -    let is_top = pos.y % 2 == 0;
       -
       -    let offset = editor
       -        .buffer_view
       -        .lock()
       -        .get_edit_state()
       -        .get_cur_layer()
       -        .unwrap()
       -        .get_offset();
       -    let pos = Position::new(pos.x, text_y) + offset;
       -    let block = editor.get_char(pos);
       -
       -    let mut upper_block_color = 0;
       -    let mut lower_block_color = 0;
       -    let mut left_block_color = 0;
       -    let mut right_block_color = 0;
       -    let mut is_blocky = false;
       -    let mut is_vertically_blocky = false;
       -    match block.ch as u8 {
       -        0 | 32 | 255 => {
       -            upper_block_color = block.attribute.get_background();
       -            lower_block_color = block.attribute.get_background();
       -            is_blocky = true;
       -        }
       -        220 => {
       -            upper_block_color = block.attribute.get_background();
       -            lower_block_color = block.attribute.get_foreground();
       -            is_blocky = true;
       -        }
       -        223 => {
       -            upper_block_color = block.attribute.get_foreground();
       -            lower_block_color = block.attribute.get_background();
       -            is_blocky = true;
       -        }
       -        219 => {
       -            upper_block_color = block.attribute.get_foreground();
       -            lower_block_color = block.attribute.get_foreground();
       -            is_blocky = true;
       -        }
       -        221 => {
       -            left_block_color = block.attribute.get_foreground();
       -            right_block_color = block.attribute.get_background();
       -            is_vertically_blocky = true;
       -        }
       -        222 => {
       -            left_block_color = block.attribute.get_background();
       -            right_block_color = block.attribute.get_foreground();
       -            is_vertically_blocky = true;
       -        }
       -        _ => {
       -            if block.attribute.get_foreground() == block.attribute.get_background() {
       -                is_blocky = true;
       -                upper_block_color = block.attribute.get_foreground();
       -                lower_block_color = block.attribute.get_foreground();
       -            } else {
       -                is_blocky = false;
       -            }
       -        }
       -    }
       -    (
       -        pos,
       -        text_y,
       -        is_blocky,
       -        is_vertically_blocky,
       -        upper_block_color,
       -        lower_block_color,
       -        left_block_color,
       -        right_block_color,
       -        is_top,
       -        block.attribute.get_foreground(),
       -        block.attribute.get_background(),
       -    )
       -}
       -
       -pub fn set_half_block(editor: &AnsiEditor, pos: Position, col: u32) {
       -    let w = editor.buffer_view.lock().get_buffer().get_width();
       -    let h = editor.buffer_view.lock().get_buffer().get_height();
       -
       -    if pos.x < 0 || pos.x >= w || pos.y < 0 || pos.y >= h * 2 {
       -        return;
       -    }
       -
       -    let (
       -        _,
       -        text_y,
       -        is_blocky,
       -        _,
       -        upper_block_color,
       -        lower_block_color,
       -        _,
       -        _,
       -        is_top,
       -        _,
       -        block_back,
       -    ) = get_half_block(editor, pos);
       -
       -    let pos = Position::new(pos.x, text_y);
       -    if is_blocky {
       -        if (is_top && lower_block_color == col) || (!is_top && upper_block_color == col) {
       -            if let Some(layer) = editor
       -                .buffer_view
       -                .lock()
       -                .get_buffer_mut()
       -                .get_overlay_layer()
       -            {
       -                layer.set_char(
       -                    pos,
       -                    AttributedChar::new('\u{00DB}', TextAttribute::new(col, 0)),
       -                );
       -            }
       -        } else if is_top {
       -            if let Some(layer) = editor
       -                .buffer_view
       -                .lock()
       -                .get_buffer_mut()
       -                .get_overlay_layer()
       -            {
       -                layer.set_char(
       -                    pos,
       -                    AttributedChar::new('\u{00DF}', TextAttribute::new(col, lower_block_color)),
       -                );
       -            }
       -        } else if let Some(layer) = editor
       -            .buffer_view
       -            .lock()
       -            .get_buffer_mut()
       -            .get_overlay_layer()
       -        {
       -            layer.set_char(
       -                pos,
       -                AttributedChar::new('\u{00DC}', TextAttribute::new(col, upper_block_color)),
       -            );
       -        }
       -    } else if is_top {
       -        if let Some(layer) = editor
       -            .buffer_view
       -            .lock()
       -            .get_buffer_mut()
       -            .get_overlay_layer()
       -        {
       -            layer.set_char(
       -                pos,
       -                AttributedChar::new('\u{00DF}', TextAttribute::new(col, block_back)),
       -            );
       -        }
       -    } else if let Some(layer) = editor
       -        .buffer_view
       -        .lock()
       -        .get_buffer_mut()
       -        .get_overlay_layer()
       -    {
       -        layer.set_char(
       -            pos,
       -            AttributedChar::new('\u{00DC}', TextAttribute::new(col, block_back)),
       -        );
       -    }
       -    optimize_block(editor, Position::new(pos.x, text_y));
       -}
       -
       -fn optimize_block(editor: &AnsiEditor, pos: Position) {
       -    let block = if let Some(layer) = editor
       -        .buffer_view
       -        .lock()
       -        .get_buffer_mut()
       -        .get_overlay_layer()
       -    {
       -        layer.get_char(pos)
       -    } else {
       -        AttributedChar::default()
       -    };
       -
       -    if block.attribute.get_foreground() == 0 {
       -        if block.attribute.get_background() == 0 || block.ch == '\u{00DB}' {
       -            if let Some(layer) = editor
       -                .buffer_view
       -                .lock()
       -                .get_buffer_mut()
       -                .get_overlay_layer()
       -            {
       -                layer.set_char(pos, AttributedChar::default());
       -            }
       -        } else {
       -            match block.ch as u8 {
       -                220 => {
       -                    if let Some(layer) = editor
       -                        .buffer_view
       -                        .lock()
       -                        .get_buffer_mut()
       -                        .get_overlay_layer()
       -                    {
       -                        layer.set_char(
       -                            pos,
       -                            AttributedChar::new(
       -                                '\u{00DF}',
       -                                TextAttribute::new(
       -                                    block.attribute.get_background(),
       -                                    block.attribute.get_foreground(),
       -                                ),
       -                            ),
       -                        );
       -                    }
       -                }
       -                223 => {
       -                    if let Some(layer) = editor
       -                        .buffer_view
       -                        .lock()
       -                        .get_buffer_mut()
       -                        .get_overlay_layer()
       -                    {
       -                        layer.set_char(
       -                            pos,
       -                            AttributedChar::new(
       -                                '\u{00DC}',
       -                                TextAttribute::new(
       -                                    block.attribute.get_background(),
       -                                    block.attribute.get_foreground(),
       -                                ),
       -                            ),
       -                        );
       -                    }
       -                }
       -                _ => {}
       -            }
       -        }
       -    } else if block.attribute.get_foreground() < 8 && block.attribute.get_background() >= 8 {
       -        let (_, _, is_blocky, is_vertically_blocky, _, _, _, _, _, _, _) =
       -            get_half_block(editor, pos);
       -
       -        if is_blocky {
       -            match block.ch as u8 {
       -                220 => {
       -                    if let Some(layer) = editor
       -                        .buffer_view
       -                        .lock()
       -                        .get_buffer_mut()
       -                        .get_overlay_layer()
       -                    {
       -                        layer.set_char(
       -                            pos,
       -                            AttributedChar::new(
       -                                '\u{00DF}',
       -                                TextAttribute::new(
       -                                    block.attribute.get_background(),
       -                                    block.attribute.get_foreground(),
       -                                ),
       -                            ),
       -                        );
       -                    }
       -                }
       -                223 => {
       -                    if let Some(layer) = editor
       -                        .buffer_view
       -                        .lock()
       -                        .get_buffer_mut()
       -                        .get_overlay_layer()
       -                    {
       -                        layer.set_char(
       -                            pos,
       -                            AttributedChar::new(
       -                                '\u{00DC}',
       -                                TextAttribute::new(
       -                                    block.attribute.get_background(),
       -                                    block.attribute.get_foreground(),
       -                                ),
       -                            ),
       -                        );
       -                    }
       -                }
       -                _ => {}
       -            }
       -        } else if is_vertically_blocky {
       -            match block.ch as u8 {
       -                221 => {
       -                    if let Some(layer) = editor
       -                        .buffer_view
       -                        .lock()
       -                        .get_buffer_mut()
       -                        .get_overlay_layer()
       -                    {
       -                        layer.set_char(
       -                            pos,
       -                            AttributedChar::new(
       -                                '\u{00DE}',
       -                                TextAttribute::new(
       -                                    block.attribute.get_background(),
       -                                    block.attribute.get_foreground(),
       -                                ),
       -                            ),
       -                        );
       -                    }
       -                }
       -                222 => {
       -                    if let Some(layer) = editor
       -                        .buffer_view
       -                        .lock()
       -                        .get_buffer_mut()
       -                        .get_overlay_layer()
       -                    {
       -                        layer.set_char(
       -                            pos,
       -                            AttributedChar::new(
       -                                '\u{00DD}',
       -                                TextAttribute::new(
       -                                    block.attribute.get_background(),
       -                                    block.attribute.get_foreground(),
       -                                ),
       -                            ),
       -                        );
       -                    }
       -                }
       -                _ => {}
       -            }
       -        }
       -    }
       -}
 (DIR) diff --git a/src/model/tools/mod.rs b/src/model/tools/mod.rs
       @@ -20,14 +20,11 @@ mod icons;
        use eframe::egui::{self, Response};
        use egui_extras::RetainedImage;
        use i18n_embed_fl::fl;
       -use icy_engine::{AttributedChar, Position, TextAttribute};
       +use icy_engine::Position;
        use icy_engine_egui::TerminalCalc;
       -pub use scan_lines::*;
        
        use crate::{AnsiEditor, Event, Message};
        
       -pub mod scan_lines;
       -
        #[derive(Copy, Clone, Debug)]
        pub enum MKey {
            Character(u16),
       @@ -193,162 +190,3 @@ fn toolbar_pos_sel_text(editor: &AnsiEditor, show_selection: bool) -> String {
                )
            }
        }
       -
       -#[derive(Clone, Copy, Debug, PartialEq)]
       -pub enum DrawMode {
       -    Line,
       -    Char,
       -    Shade,
       -    Colorize,
       -    //   Outline,
       -}
       -
       -trait Plottable {
       -    fn get_draw_mode(&self) -> DrawMode;
       -
       -    fn get_use_fore(&self) -> bool;
       -    fn get_use_back(&self) -> bool;
       -    fn get_char_code(&self) -> char;
       -}
       -/*
       -pub const OUTLINE_TABLE: [[u8; 11]; 4] = [
       -    // UL,   UR,   LR,
       -    [
       -        0xDA, 0xBF, 0xC0, 0xD9, 0xC4, 0xBC, 0xC3, 0xB4, 0xC1, 0xC2, 0xC5,
       -    ],
       -    [
       -        0xC9, 0xBB, 0xC8, 0xBC, 0xCD, 0xBA, 0xCC, 0xB9, 0xCA, 0xCB, 0xCE,
       -    ],
       -    [
       -        0xD5, 0xB8, 0xD4, 0xBE, 0xCD, 0xB3, 0xC6, 0xB5, 0xCF, 0xD1, 0xD8,
       -    ],
       -    [
       -        0xD6, 0xB7, 0xD3, 0xBD, 0xC4, 0xBA, 0xC7, 0xB6, 0xD0, 0xD2, 0xD7,
       -    ],
       -];
       -
       -const CORNER_UPPER_LEFT: usize = 0;
       -const CORNER_UPPER_RIGHT: usize = 1;
       -const CORNER_LOWER_LEFT: usize = 2;
       -const CORNER_LOWER_RIGHT: usize = 3;
       -
       -const HORIZONTAL_CHAR: usize = 4;
       -const VERTICAL_CHAR: usize = 5;
       -
       -const VERT_RIGHT_CHAR: usize = 6;
       -const VERT_LEFT_CHAR: usize = 7;
       -
       -const HORIZ_UP_CHAR: usize = 8;
       -const HORIZ_DOWN_CHAR: usize = 9;
       -const CROSS_CHAR: usize = 11;
       - */
       -fn plot_point(editor: &AnsiEditor, tool: &dyn Plottable, pos: Position) {
       -    let ch = editor.get_char_from_cur_layer(pos);
       -    let editor_attr = editor.buffer_view.lock().get_caret().get_attribute();
       -    let mut attribute = ch.attribute;
       -    if !ch.is_visible() {
       -        attribute = TextAttribute::default();
       -    }
       -    if tool.get_use_back() {
       -        attribute.set_background(editor_attr.get_background());
       -    }
       -    if tool.get_use_fore() {
       -        attribute.set_is_bold(false);
       -        attribute.set_foreground(editor_attr.get_foreground());
       -    }
       -
       -    attribute.set_font_page(editor_attr.get_font_page());
       -    match tool.get_draw_mode() {
       -        DrawMode::Line => {
       -            if let Some(layer) = editor
       -                .buffer_view
       -                .lock()
       -                .get_buffer_mut()
       -                .get_overlay_layer()
       -            {
       -                layer.set_char(
       -                    pos,
       -                    AttributedChar::new(unsafe { char::from_u32_unchecked(219) }, attribute),
       -                );
       -            }
       -        }
       -        DrawMode::Char => {
       -            if let Some(layer) = editor
       -                .buffer_view
       -                .lock()
       -                .get_buffer_mut()
       -                .get_overlay_layer()
       -            {
       -                layer.set_char(pos, AttributedChar::new(tool.get_char_code(), attribute));
       -            }
       -        }
       -        DrawMode::Shade => {
       -            let mut char_code = SHADE_GRADIENT[0];
       -            if ch.ch == SHADE_GRADIENT[SHADE_GRADIENT.len() - 1] {
       -                char_code = SHADE_GRADIENT[SHADE_GRADIENT.len() - 1];
       -            } else {
       -                for i in 0..SHADE_GRADIENT.len() - 1 {
       -                    if ch.ch == SHADE_GRADIENT[i] {
       -                        char_code = SHADE_GRADIENT[i + 1];
       -                        break;
       -                    }
       -                }
       -            }
       -            if let Some(layer) = editor
       -                .buffer_view
       -                .lock()
       -                .get_buffer_mut()
       -                .get_overlay_layer()
       -            {
       -                layer.set_char(pos, AttributedChar::new(char_code, attribute));
       -            }
       -        }
       -        DrawMode::Colorize => {
       -            if let Some(layer) = editor
       -                .buffer_view
       -                .lock()
       -                .get_buffer_mut()
       -                .get_overlay_layer()
       -            {
       -                layer.set_char(pos, AttributedChar::new(ch.ch, attribute));
       -            }
       -        } /*
       -          DrawMode::Outline => {
       -              if let Some(layer) = editor
       -                  .buffer_view
       -                  .lock()
       -                  .get_buffer_mut()
       -                  .get_overlay_layer()
       -              {
       -                  let left = layer.get_char(pos - Position::new(1, 0));
       -                  let right = layer.get_char(pos + Position::new(1, 0));
       -                  let up = layer.get_char(pos - Position::new(0, 1));
       -                  let down = layer.get_char(pos + Position::new(0, 1));
       -
       -                  let idx = if left.is_transparent()
       -                      && right.is_transparent()
       -                      && up.is_transparent()
       -                      && down.is_transparent()
       -                  {
       -                      CORNER_UPPER_LEFT
       -                  } else if left.ch as u8 == OUTLINE_TABLE[0][CORNER_UPPER_LEFT]
       -                      || left.ch as u8 == OUTLINE_TABLE[0][HORIZONTAL_CHAR]
       -                  {
       -                      HORIZONTAL_CHAR
       -                  } else {
       -                      VERTICAL_CHAR
       -                  };
       -
       -                  layer.set_char(
       -                      pos,
       -                      AttributedChar::new(
       -                          unsafe { char::from_u32_unchecked(OUTLINE_TABLE[0][idx] as u32) },
       -                          attribute,
       -                      ),
       -                  );
       -              }
       -          }*/
       -    }
       -}
       -
       -pub static SHADE_GRADIENT: [char; 4] = ['\u{00B0}', '\u{00B1}', '\u{00B2}', '\u{00DB}'];
 (DIR) diff --git a/src/model/tools/pencil_imp.rs b/src/model/tools/pencil_imp.rs
       @@ -3,147 +3,37 @@ use std::{cell::RefCell, rc::Rc};
        use eframe::egui::{self};
        use egui_extras::RetainedImage;
        use i18n_embed_fl::fl;
       -use icy_engine::{editor::AtomicUndoGuard, AttributedChar, Rectangle};
       +use icy_engine::{editor::AtomicUndoGuard, TextAttribute};
        use icy_engine_egui::TerminalCalc;
        
       -use crate::{model::ScanLines, AnsiEditor, Event, Message};
       +use crate::{
       +    paint::{plot_point, BrushMode, ColorMode, PointRole},
       +    AnsiEditor, Event, Message,
       +};
        
       -use super::{brush_imp::draw_glyph, line_imp::set_half_block, Position, Tool};
       -
       -#[derive(PartialEq, Eq, Default)]
       -pub enum PencilType {
       -    #[default]
       -    HalfBlock,
       -    Shade,
       -    Solid,
       -    Color,
       -}
       +use super::{Position, Tool};
        
        pub struct PencilTool {
       -    use_fore: bool,
       -    use_back: bool,
            char_code: std::rc::Rc<std::cell::RefCell<char>>,
            undo_op: Option<AtomicUndoGuard>,
       +    draw_mode: BrushMode,
       +    color_mode: ColorMode,
       +    pub attr: TextAttribute,
        
            last_pos: Position,
            cur_pos: Position,
       -    brush_type: PencilType,
        }
        
        impl Default for PencilTool {
            fn default() -> Self {
                Self {
       -            use_back: true,
       -            use_fore: true,
                    undo_op: None,
       -            brush_type: crate::model::pencil_imp::PencilType::Shade,
       +            draw_mode: BrushMode::HalfBlock,
       +            color_mode: ColorMode::Both,
                    char_code: Rc::new(RefCell::new('\u{00B0}')),
                    last_pos: Position::default(),
                    cur_pos: Position::default(),
       -        }
       -    }
       -}
       -
       -impl PencilTool {
       -    fn paint_brush(&self, editor: &mut AnsiEditor, pos: Position) {
       -        let center = pos;
       -        let gradient = ['\u{00B0}', '\u{00B1}', '\u{00B2}', '\u{00DB}'];
       -        let offset = if let Some(layer) = editor.buffer_view.lock().get_edit_state().get_cur_layer()
       -        {
       -            layer.get_offset()
       -        } else {
       -            Position::default()
       -        };
       -
       -        let use_selection = editor
       -            .buffer_view
       -            .lock()
       -            .get_edit_state()
       -            .is_something_selected();
       -
       -        if use_selection
       -            && !editor
       -                .buffer_view
       -                .lock()
       -                .get_edit_state()
       -                .get_is_selected(pos + offset)
       -        {
       -            return;
       -        }
       -
       -        match self.brush_type {
       -            PencilType::HalfBlock => {
       -                let mut lines = ScanLines::new(1);
       -                let pos = editor.half_block_click_pos;
       -                lines.add_line(Position::new(pos.x, pos.y), Position::new(pos.x, pos.y));
       -                let draw = move |rect: Rectangle| {
       -                    let col = editor
       -                        .buffer_view
       -                        .lock()
       -                        .get_caret()
       -                        .get_attribute()
       -                        .get_foreground();
       -                    for y in 0..rect.size.height {
       -                        for x in 0..rect.size.width {
       -                            set_half_block(
       -                                editor,
       -                                Position::new(rect.start.x + x, rect.start.y + y),
       -                                col,
       -                            );
       -                        }
       -                    }
       -                };
       -                lines.fill(draw);
       -            }
       -            PencilType::Shade => {
       -                let ch = editor.get_char_from_cur_layer(center);
       -                let attribute = editor.buffer_view.lock().get_caret().get_attribute();
       -
       -                let mut char_code = gradient[0];
       -                if ch.ch == gradient[gradient.len() - 1] {
       -                    char_code = gradient[gradient.len() - 1];
       -                } else {
       -                    for i in 0..gradient.len() - 1 {
       -                        if ch.ch == gradient[i] {
       -                            char_code = gradient[i + 1];
       -                            break;
       -                        }
       -                    }
       -                }
       -                editor.set_char(center, AttributedChar::new(char_code, attribute));
       -            }
       -            PencilType::Solid => {
       -                let attribute = editor.buffer_view.lock().get_caret().get_attribute();
       -                editor.set_char(
       -                    center,
       -                    AttributedChar::new(*self.char_code.borrow(), attribute),
       -                );
       -            }
       -            PencilType::Color => {
       -                let ch = editor.get_char_from_cur_layer(center);
       -                let mut attribute = ch.attribute;
       -                if self.use_fore {
       -                    attribute.set_foreground(
       -                        editor
       -                            .buffer_view
       -                            .lock()
       -                            .get_caret()
       -                            .get_attribute()
       -                            .get_foreground(),
       -                    );
       -                }
       -                if self.use_back {
       -                    attribute.set_background(
       -                        editor
       -                            .buffer_view
       -                            .lock()
       -                            .get_caret()
       -                            .get_attribute()
       -                            .get_background(),
       -                    );
       -                }
       -                editor.set_char(center, AttributedChar::new(ch.ch, attribute));
       -            }
       +            attr: icy_engine::TextAttribute::default(),
                }
            }
        }
       @@ -163,49 +53,10 @@ impl Tool for PencilTool {
                ui: &mut egui::Ui,
                editor_opt: Option<&AnsiEditor>,
            ) -> Option<Message> {
       -        let mut result = None;
       -        ui.vertical_centered(|ui| {
       -            ui.horizontal(|ui| {
       -                if ui
       -                    .selectable_label(self.use_fore, fl!(crate::LANGUAGE_LOADER, "tool-fg"))
       -                    .clicked()
       -                {
       -                    self.use_fore = !self.use_fore;
       -                }
       -                if ui
       -                    .selectable_label(self.use_back, fl!(crate::LANGUAGE_LOADER, "tool-bg"))
       -                    .clicked()
       -                {
       -                    self.use_back = !self.use_back;
       -                }
       -            });
       -        });
       -        ui.radio_value(
       -            &mut self.brush_type,
       -            PencilType::HalfBlock,
       -            fl!(crate::LANGUAGE_LOADER, "tool-half-block"),
       -        );
       -        ui.radio_value(
       -            &mut self.brush_type,
       -            PencilType::Shade,
       -            fl!(crate::LANGUAGE_LOADER, "tool-shade"),
       -        );
       -        ui.horizontal(|ui| {
       -            ui.radio_value(
       -                &mut self.brush_type,
       -                PencilType::Solid,
       -                fl!(crate::LANGUAGE_LOADER, "tool-character"),
       -            );
       -            if let Some(editor) = editor_opt {
       -                result = draw_glyph(ui, editor, &self.char_code);
       -            }
       -        });
       -        ui.radio_value(
       -            &mut self.brush_type,
       -            PencilType::Color,
       -            fl!(crate::LANGUAGE_LOADER, "tool-colorize"),
       -        );
       -        result
       +        self.color_mode.show_ui(ui);
       +        self.draw_mode
       +            .show_ui(ui, editor_opt, self.char_code.clone());
       +        None
            }
        
            fn handle_click(
       @@ -221,7 +72,13 @@ impl Tool for PencilTool {
                    let _op: AtomicUndoGuard =
                        editor.begin_atomic_undo(fl!(crate::LANGUAGE_LOADER, "undo-pencil"));
                    editor.clear_overlay_layer();
       -            self.paint_brush(editor, pos);
       +            plot_point(
       +                &mut editor.buffer_view.lock(),
       +                editor.half_block_click_pos,
       +                self.draw_mode.clone(),
       +                self.color_mode,
       +                PointRole::Line,
       +            );
                    editor.join_overlay(fl!(crate::LANGUAGE_LOADER, "undo-pencil"));
                }
                None
       @@ -245,7 +102,14 @@ impl Tool for PencilTool {
                editor: &mut AnsiEditor,
                _calc: &TerminalCalc,
            ) -> egui::Response {
       -        self.paint_brush(editor, editor.drag_pos.cur);
       +        plot_point(
       +            &mut editor.buffer_view.lock(),
       +            editor.half_block_click_pos,
       +            self.draw_mode.clone(),
       +            self.color_mode,
       +            PointRole::Line,
       +        );
       +
                self.last_pos = editor.drag_pos.cur;
                self.cur_pos = editor.drag_pos.cur;
        
       @@ -257,7 +121,13 @@ impl Tool for PencilTool {
                self.last_pos = editor.drag_pos.cur;
                self.cur_pos = editor.drag_pos.cur;
                editor.clear_overlay_layer();
       -        self.paint_brush(editor, editor.drag_pos.cur);
       +        plot_point(
       +            &mut editor.buffer_view.lock(),
       +            editor.half_block_click_pos,
       +            self.draw_mode.clone(),
       +            self.color_mode,
       +            PointRole::Line,
       +        );
        
                Event::None
            }
 (DIR) diff --git a/src/model/tools/scan_lines.rs b/src/model/tools/scan_lines.rs
       @@ -1,411 +0,0 @@
       -use std::{
       -    cmp::{max, min},
       -    collections::HashMap,
       -    f64::consts,
       -};
       -
       -use icy_engine::Rectangle;
       -
       -use super::Position;
       -
       -// Code from Pablodraw - I liked the approach and I needed a better ellipse drawing algorithm.
       -// translated from https://github.com/cwensley/pablodraw/blob/main/Source/Pablo/Drawing/ScanLines.cs
       -
       -pub struct ScanLines {
       -    line_thickness: i32,
       -    rows: HashMap<i32, ScanRow>,
       -}
       -
       -impl ScanLines {
       -    const RAD2DEG: f64 = 180.0 / consts::PI;
       -    // const deg2rad: f64 = consts::PI / 180.0;
       -
       -    pub fn add(&mut self, x: i32, y: i32) {
       -        if let Some(row) = self.rows.get_mut(&y) {
       -            row.add(x);
       -        } else {
       -            let mut row = ScanRow::new();
       -            row.add(x);
       -            self.rows.insert(y, row);
       -        }
       -    }
       -
       -    pub fn add_ellipse(&mut self, rectangle: Rectangle) {
       -        let mut rw = rectangle.size.width;
       -        let mut rh = rectangle.size.height;
       -
       -        if rw < 2 {
       -            rw = 2;
       -        }
       -        if rh < 2 {
       -            rh = 2;
       -        }
       -
       -        if (rw % 2) == 0 {
       -            rw += 1;
       -        }
       -        if (rh % 2) == 0 {
       -            rh += 1;
       -        }
       -
       -        let mx = rectangle.start.x + rw / 2;
       -        let my = rectangle.start.y + rh / 2;
       -
       -        self.add_ellipse_internal(mx, my, rw / 2, rh / 2 /*, 0, 360*/);
       -    }
       -
       -    fn add_ellipse_internal(
       -        &mut self,
       -        x: i32,
       -        y: i32,
       -        radius_x: i32,
       -        radius_y: i32, /*, mut start_angle: i32, mut end_angle: i32*/
       -    ) {
       -        // check if valid angles
       -        //if start_angle > end_angle {
       -        //    std::mem::swap(&mut start_angle, &mut end_angle);
       -        //}
       -
       -        let radius_x = max(1, radius_x);
       -        let radius_y = max(1, radius_y);
       -
       -        let dx = radius_x * 2;
       -        let dy = radius_y * 2;
       -        let b1 = dy & 1;
       -        let mut stop_x = 4 * (1 - dx) * dy * dy;
       -        let mut stop_y = 4 * (b1 + 1) * dx * dx; // error increment
       -        let mut err = stop_x + stop_y + b1 * dx * dx; // error of 1 step
       -
       -        let mut xoffset = radius_x;
       -        let mut yoffset = 0;
       -        let inc_x = 8 * dx * dx;
       -        let inc_y = 8 * dy * dy;
       -
       -        let aspect = (radius_x as f64) / (radius_y as f64);
       -
       -        // calculate horizontal fill angle
       -        let horizontal_angle = if radius_x < radius_y {
       -            90.0 - (45.0 * aspect)
       -        } else {
       -            45.0 / aspect
       -        };
       -        let horizontal_angle = horizontal_angle.round() as i32;
       -
       -        loop {
       -            let e2 = 2 * err;
       -            let angle = ((yoffset as f64 * aspect / (xoffset as f64)).atan() * ScanLines::RAD2DEG)
       -                .round() as i32;
       -            self.symmetry_scan(
       -                x, y, /*start_angle, end_angle, */ xoffset,
       -                yoffset, /*, angle, angle <= horizontal_angle*/
       -            );
       -            if (angle - horizontal_angle).abs() < 1 {
       -                self.symmetry_scan(
       -                    x, y, /*start_angle, end_angle,*/ xoffset,
       -                    yoffset, /*, angle, angle > horizontal_angle*/
       -                );
       -            }
       -
       -            if e2 <= stop_y {
       -                yoffset += 1;
       -                stop_y += inc_x;
       -                err += stop_y;
       -            }
       -            if e2 >= stop_x {
       -                xoffset -= 1;
       -                stop_x += inc_y;
       -                err += stop_x;
       -            }
       -            if xoffset < 0 {
       -                break;
       -            }
       -        }
       -
       -        xoffset += 1;
       -        while yoffset < radius_y {
       -            let angle = ((yoffset as f64 * aspect / (xoffset as f64)).atan() * ScanLines::RAD2DEG)
       -                .round() as i32;
       -            self.symmetry_scan(
       -                x, y, /*start_angle, end_angle, */ xoffset,
       -                yoffset, /*, angle as i32, angle <= horizontal_angle*/
       -            );
       -            if angle == horizontal_angle {
       -                self.symmetry_scan(
       -                    x, y, /*start_angle, end_angle,*/ xoffset,
       -                    yoffset, /*, angle as i32, angle > horizontal_angle*/
       -                );
       -            }
       -            yoffset += 1;
       -        }
       -    }
       -
       -    fn add_horizontal(&mut self, x: i32, y: i32, count: i32) {
       -        if count > 0 {
       -            for i in 0..count {
       -                self.add(x + i, y);
       -            }
       -        } else {
       -            for i in (0..count).rev() {
       -                self.add(x + i, y);
       -            }
       -        }
       -    }
       -
       -    /*fn add_vertical(&mut self, x: i32, y: i32, count: i32) {
       -        if count > 0 {
       -            for i in 0..count {
       -                self.add(x, y + i);
       -            }
       -        } else {
       -            for i in (0..count).rev() {
       -                self.add(x, y + i);
       -            }
       -        }
       -    }*/
       -
       -    pub fn fill<T>(&self, mut draw: T)
       -    where
       -        T: FnMut(Rectangle),
       -    {
       -        for row in &self.rows {
       -            let (min, max) = row.1.get_min_max();
       -            let r = Rectangle::from_coords(min, *row.0, max + 1, *row.0 + 1);
       -            draw(r);
       -        }
       -    }
       -
       -    /* fn in_angle(angle: i32, start_angle: i32, end_angle: i32) -> bool {
       -        angle >= start_angle && angle <= end_angle
       -    }*/
       -
       -    /*pub fn is_drawn(&self, point: Position) -> bool {
       -        if let Some(row) = self.rows.get(&point.y) {
       -            row.points.contains(&point.x)
       -        } else {
       -            false
       -        }
       -    }*/
       -
       -    // simple https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
       -    // maybe worth to explore https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm
       -    pub fn add_line(&mut self, mut pos0: Position, pos1: Position) {
       -        let dx = (pos1.x - pos0.x).abs();
       -        let sx = if pos0.x < pos1.x { 1 } else { -1 };
       -        let dy = -(pos1.y - pos0.y).abs();
       -        let sy = if pos0.y < pos1.y { 1 } else { -1 };
       -        let mut error = dx + dy;
       -
       -        loop {
       -            self.add(pos0.x, pos0.y);
       -
       -            if pos0.x == pos1.x && pos0.y == pos1.y {
       -                break;
       -            }
       -            let e2 = 2 * error;
       -            if e2 >= dy {
       -                if pos0.x == pos1.x {
       -                    break;
       -                }
       -                error += dy;
       -                pos0.x += sx;
       -            }
       -            if e2 <= dx {
       -                if pos0.y == pos1.y {
       -                    break;
       -                }
       -                error += dx;
       -                pos0.y += sy;
       -            }
       -        }
       -    }
       -
       -    pub fn add_rectangle(&mut self, rectangle: Rectangle) {
       -        for i in 0..rectangle.size.height {
       -            self.add_horizontal(
       -                rectangle.start.x,
       -                rectangle.start.y + i,
       -                rectangle.size.width,
       -            );
       -        }
       -    }
       -
       -    /*  pub fn is_inside(&self, point: Position) -> bool {
       -        if let Some(row) = self.rows.get(&point.y) {
       -            let (min, max) = row.get_min_max();
       -            min <= point.x && point.x <= max
       -        } else {
       -            false
       -        }
       -    }*/
       -
       -    pub fn new(line_thickness: i32) -> Self {
       -        ScanLines {
       -            line_thickness,
       -            rows: HashMap::new(),
       -        }
       -    }
       -
       -    pub fn outline(&self) -> Vec<Rectangle> {
       -        let mut result = Vec::new();
       -        let mut lastx = 0;
       -
       -        let mut rows = Vec::new();
       -
       -        for i in &self.rows {
       -            let (min, max) = i.1.get_min_max();
       -            rows.push((*i.0, min, max));
       -        }
       -        rows.sort_by(|x, y| x.0.cmp(&y.0));
       -        // trace min edge
       -        for i in 0..rows.len() {
       -            let row = rows[i];
       -
       -            let cur_y = row.0;
       -            let cur_x = row.1;
       -            let last = i == rows.len() - 1;
       -            let first = i == 0;
       -
       -            if !first && !last {
       -                let nextx = rows[i + 1].1;
       -                if cur_x < lastx {
       -                    result.push(Rectangle::from_coords(cur_x, cur_y, lastx, cur_y + 1));
       -                } else if cur_x > lastx + 1 {
       -                    result.push(Rectangle::from_coords(
       -                        lastx + 1,
       -                        cur_y - 1,
       -                        cur_x - 1,
       -                        cur_y - 1,
       -                    ));
       -                    result.push(Rectangle::from_coords(cur_x, cur_y, cur_x + 1, cur_y + 1));
       -                } else {
       -                    result.push(Rectangle::from_coords(cur_x, cur_y, cur_x + 1, cur_y + 1));
       -                }
       -                if nextx > cur_x {
       -                    result.push(Rectangle::from_coords(cur_x, cur_y, nextx, cur_y + 1));
       -                }
       -            }
       -            lastx = cur_x;
       -        }
       -
       -        // trace max edge
       -        for i in 0..rows.len() {
       -            let row = rows[i];
       -            let cur_y = row.0;
       -            let cur_x = row.2;
       -            let last = i == rows.len() - 1;
       -            let first = i == 0;
       -
       -            if !first && !last {
       -                let nextx = rows[i + 1].2;
       -                if cur_x > lastx {
       -                    result.push(Rectangle::from_coords(
       -                        lastx + 1,
       -                        cur_y,
       -                        cur_x + 1,
       -                        cur_y + 1,
       -                    ));
       -                } else if cur_x < lastx - 1 {
       -                    result.push(Rectangle::from_coords(cur_x + 1, cur_y - 1, lastx, cur_y));
       -                    result.push(Rectangle::from_coords(cur_x, cur_y, cur_x + 1, cur_y + 1));
       -                } else {
       -                    result.push(Rectangle::from_coords(cur_x, cur_y, cur_x + 1, cur_y + 1));
       -                }
       -                if nextx < cur_x {
       -                    result.push(Rectangle::from_coords(
       -                        nextx + 1,
       -                        cur_y,
       -                        cur_x + 1,
       -                        cur_y + 1,
       -                    ));
       -                }
       -            }
       -            lastx = cur_x;
       -        }
       -
       -        // fill top/bottom
       -        if rows.is_empty() {
       -            return result;
       -        }
       -        let row = rows[0];
       -        result.push(Rectangle::from_coords(row.1, row.0, row.2 + 1, row.0 + 1));
       -        let row = rows[rows.len() - 1];
       -        result.push(Rectangle::from_coords(row.1, row.0, row.2 + 1, row.0 + 1));
       -
       -        result
       -    }
       -
       -    fn symmetry_scan(
       -        &mut self,
       -        x: i32,
       -        y: i32,
       -        /* start_angle: i32, end_angle: i32, */ xoffset: i32,
       -        yoffset: i32, /*, angle: i32, horizontal: bool*/
       -    ) {
       -        if self.line_thickness == 1 {
       -            //if ScanLines::in_angle(angle, start_angle, end_angle) {
       -            self.add(x + xoffset, y - yoffset);
       -            //}
       -            //if ScanLines::in_angle(180 - angle, start_angle, end_angle) {
       -            self.add(x - xoffset, y - yoffset);
       -            //}
       -            //if ScanLines::in_angle(180 + angle, start_angle, end_angle) {
       -            self.add(x - xoffset, y + yoffset);
       -            //}
       -            //if ScanLines::in_angle(360 - angle, start_angle, end_angle) {
       -            self.add(x + xoffset, y + yoffset);
       -            //}
       -        } /*else {
       -              let offset = self.line_thickness / 2;
       -              if horizontal {
       -                  if ScanLines::in_angle(angle, start_angle, end_angle) {
       -                      self.add_horizontal(x + xoffset - offset, y - yoffset, self.line_thickness);
       -                  }
       -                  if ScanLines::in_angle(180 - angle, start_angle, end_angle) {
       -                      self.add_horizontal(x - xoffset - offset, y - yoffset, self.line_thickness);
       -                  }
       -                  if ScanLines::in_angle(180 + angle, start_angle, end_angle) {
       -                      self.add_horizontal(x - xoffset - offset, y + yoffset, self.line_thickness);
       -                  }
       -                  if ScanLines::in_angle(360 - angle, start_angle, end_angle) {
       -                      self.add_horizontal(x + xoffset - offset, y + yoffset, self.line_thickness);
       -                  }
       -              } else {
       -                  if ScanLines::in_angle(angle, start_angle, end_angle) {
       -                      self.add_vertical(x + xoffset, y - yoffset - offset, self.line_thickness);
       -                  }
       -                  if ScanLines::in_angle(180 - angle, start_angle, end_angle) {
       -                      self.add_vertical(x - xoffset, y - yoffset - offset, self.line_thickness);
       -                  }
       -                  if ScanLines::in_angle(180 + angle, start_angle, end_angle) {
       -                      self.add_vertical(x - xoffset, y + yoffset - offset, self.line_thickness);
       -                  }
       -                  if ScanLines::in_angle(360 - angle, start_angle, end_angle) {
       -                      self.add_vertical(x + xoffset, y + yoffset - offset, self.line_thickness);
       -                  }
       -              }
       -          }*/
       -    }
       -}
       -
       -struct ScanRow {
       -    pub points: Vec<i32>,
       -}
       -
       -impl ScanRow {
       -    pub fn new() -> Self {
       -        ScanRow { points: Vec::new() }
       -    }
       -    pub fn get_min_max(&self) -> (i32, i32) {
       -        let mut min_point = i32::MAX;
       -        let mut max_point = i32::MIN;
       -
       -        for i in &self.points {
       -            min_point = min(min_point, *i);
       -            max_point = max(max_point, *i);
       -        }
       -        (min_point, max_point)
       -    }
       -    pub fn add(&mut self, x: i32) {
       -        self.points.push(x);
       -    }
       -}
 (DIR) diff --git a/src/paint/ellipse.rs b/src/paint/ellipse.rs
       @@ -0,0 +1,106 @@
       +use icy_engine::Position;
       +use icy_engine_egui::BufferView;
       +
       +use super::{plot_point, BrushMode, ColorMode, PointRole};
       +
       +fn get_ellipse_points(from: Position, to: Position) -> Vec<Position> {
       +    let mut result = Vec::new();
       +
       +    let rx = (from.x - to.x).abs() / 2;
       +    let ry = (from.y - to.y).abs() / 2;
       +
       +    let xc = (from.x + to.x) / 2;
       +    let yc = (from.y + to.y) / 2;
       +
       +    let mut x = 0;
       +    let mut y = ry;
       +
       +    let mut d1 = (ry * ry) - (rx * rx * ry) + (rx * rx) / 4;
       +    let mut dx = 2 * ry * ry * x;
       +    let mut dy = 2 * rx * rx * y;
       +
       +    while dx < dy {
       +        result.push(Position::new(-x + xc, y + yc));
       +        result.push(Position::new(x + xc, y + yc));
       +        result.push(Position::new(-x + xc, -y + yc));
       +        result.push(Position::new(x + xc, -y + yc));
       +
       +        if d1 < 0 {
       +            x += 1;
       +            dx += 2 * ry * ry;
       +            d1 += dx + (ry * ry);
       +        } else {
       +            x += 1;
       +            y -= 1;
       +            dx += 2 * ry * ry;
       +            dy -= 2 * rx * rx;
       +            d1 += dx - dy + (ry * ry);
       +        }
       +    }
       +
       +    let mut d2 = ((ry * ry) * ((x/*+ 0.5f*/) * (x/*+ 0.5f*/))) + ((rx * rx) * ((y - 1) * (y - 1)))
       +        - (rx * rx * ry * ry);
       +
       +    while y >= 0 {
       +        result.push(Position::new(-x + xc, y + yc));
       +        result.push(Position::new(x + xc, y + yc));
       +        result.push(Position::new(-x + xc, -y + yc));
       +        result.push(Position::new(x + xc, -y + yc));
       +        if d2 > 0 {
       +            y -= 1;
       +            dy -= 2 * rx * rx;
       +            d2 += (rx * rx) - dy;
       +        } else {
       +            y -= 1;
       +            x += 1;
       +            dx += 2 * ry * ry;
       +            dy -= 2 * rx * rx;
       +            d2 += dx - dy + (rx * rx);
       +        }
       +    }
       +
       +    result
       +}
       +
       +pub fn draw_ellipse(
       +    buffer_view: &mut BufferView,
       +    from: impl Into<Position>,
       +    to: impl Into<Position>,
       +    mode: BrushMode,
       +    color_mode: ColorMode,
       +) {
       +    let from = from.into();
       +    let to = to.into();
       +    for point in get_ellipse_points(from, to) {
       +        plot_point(
       +            buffer_view,
       +            point,
       +            mode.clone(),
       +            color_mode,
       +            PointRole::Line,
       +        );
       +    }
       +}
       +
       +pub fn fill_ellipse(
       +    buffer_view: &mut BufferView,
       +    from: impl Into<Position>,
       +    to: impl Into<Position>,
       +    mode: BrushMode,
       +    color_mode: ColorMode,
       +) {
       +    let from = from.into();
       +    let to = to.into();
       +    let points = get_ellipse_points(from, to);
       +
       +    for i in 0..points.len() / 2 {
       +        let mut x1 = points[i * 2];
       +        let x2 = points[i * 2 + 1];
       +
       +        while x1.x < x2.x {
       +            plot_point(buffer_view, x1, mode.clone(), color_mode, PointRole::Line);
       +
       +            x1.x += 1;
       +        }
       +    }
       +}
 (DIR) diff --git a/src/paint/half_block.rs b/src/paint/half_block.rs
       @@ -0,0 +1,157 @@
       +use icy_engine::{AttributedChar, Position, TextAttribute};
       +
       +const FULL_BLOCK: char = 219 as char;
       +const HALF_BLOCK_TOP: char = 223 as char;
       +const HALF_BLOCK_BOTTOM: char = 220 as char;
       +const HALF_BLOCK_LEFT: char = 221 as char;
       +const HALF_BLOCK_RIGHT: char = 222 as char;
       +
       +const SPACE: char = 32 as char;
       +const ZERO: char = 0 as char;
       +const XFF: char = 255 as char;
       +
       +struct HalfBlock {
       +    pub block: AttributedChar,
       +    pub is_blocky: bool,
       +    pub is_vertically_blocky: bool,
       +
       +    pub upper_block_color: u32,
       +    pub lower_block_color: u32,
       +    pub is_top: bool,
       +}
       +
       +impl HalfBlock {
       +    pub fn from(block: AttributedChar, pos: Position) -> Self {
       +        let is_top = pos.y % 2 == 0;
       +
       +        let mut upper_block_color = 0;
       +        let mut lower_block_color = 0;
       +        let mut is_blocky = false;
       +        let mut is_vertically_blocky = false;
       +        match block.ch {
       +            ZERO | SPACE | XFF => {
       +                // all blank characters
       +                upper_block_color = block.attribute.get_background();
       +                lower_block_color = block.attribute.get_background();
       +                is_blocky = true;
       +            }
       +            HALF_BLOCK_BOTTOM => {
       +                upper_block_color = block.attribute.get_background();
       +                lower_block_color = block.attribute.get_foreground();
       +                is_blocky = true;
       +            }
       +            HALF_BLOCK_TOP => {
       +                upper_block_color = block.attribute.get_foreground();
       +                lower_block_color = block.attribute.get_background();
       +                is_blocky = true;
       +            }
       +            FULL_BLOCK => {
       +                upper_block_color = block.attribute.get_foreground();
       +                lower_block_color = block.attribute.get_foreground();
       +                is_blocky = true;
       +            }
       +            HALF_BLOCK_LEFT | HALF_BLOCK_RIGHT => {
       +                is_vertically_blocky = true;
       +            }
       +            _ => {
       +                if block.attribute.get_foreground() == block.attribute.get_background() {
       +                    is_blocky = true;
       +                    upper_block_color = block.attribute.get_foreground();
       +                    lower_block_color = block.attribute.get_foreground();
       +                } else {
       +                    is_blocky = false;
       +                }
       +            }
       +        }
       +
       +        Self {
       +            block,
       +            is_top,
       +            is_blocky,
       +            is_vertically_blocky,
       +            upper_block_color,
       +            lower_block_color,
       +        }
       +    }
       +}
       +
       +pub fn get_half_block(cur_char: AttributedChar, pos: Position, color: u32) -> AttributedChar {
       +    let half_block = HalfBlock::from(cur_char, pos);
       +
       +    let ch = if half_block.is_blocky {
       +        if (half_block.is_top && half_block.lower_block_color == color)
       +            || (!half_block.is_top && half_block.upper_block_color == color)
       +        {
       +            AttributedChar::new(FULL_BLOCK, TextAttribute::new(color, 0))
       +        } else if half_block.is_top {
       +            AttributedChar::new(
       +                HALF_BLOCK_TOP,
       +                TextAttribute::new(color, half_block.lower_block_color),
       +            )
       +        } else {
       +            AttributedChar::new(
       +                HALF_BLOCK_BOTTOM,
       +                TextAttribute::new(color, half_block.upper_block_color),
       +            )
       +        }
       +    } else if half_block.is_top {
       +        AttributedChar::new(
       +            HALF_BLOCK_TOP,
       +            TextAttribute::new(color, half_block.block.attribute.get_background()),
       +        )
       +    } else {
       +        AttributedChar::new(
       +            HALF_BLOCK_BOTTOM,
       +            TextAttribute::new(color, half_block.block.attribute.get_background()),
       +        )
       +    };
       +    optimize_block(ch, &half_block)
       +}
       +
       +fn flip_colors(attribute: icy_engine::TextAttribute) -> icy_engine::TextAttribute {
       +    let mut result = attribute;
       +    result.set_foreground(attribute.get_background());
       +    result.set_background(attribute.get_foreground());
       +    result
       +}
       +
       +fn optimize_block(mut block: AttributedChar, half_block: &HalfBlock) -> AttributedChar {
       +    if block.attribute.get_foreground() == 0 {
       +        if block.attribute.get_background() == 0 || block.ch == FULL_BLOCK {
       +            block.ch = ' ';
       +            return block;
       +        }
       +        match block.ch {
       +            HALF_BLOCK_BOTTOM => {
       +                return AttributedChar::new(HALF_BLOCK_TOP, flip_colors(block.attribute));
       +            }
       +            HALF_BLOCK_TOP => {
       +                return AttributedChar::new(HALF_BLOCK_BOTTOM, flip_colors(block.attribute));
       +            }
       +            _ => {}
       +        }
       +    } else if block.attribute.get_foreground() < 8 && block.attribute.get_background() >= 8 {
       +        if half_block.is_blocky {
       +            match block.ch {
       +                HALF_BLOCK_BOTTOM => {
       +                    return AttributedChar::new(HALF_BLOCK_TOP, flip_colors(block.attribute));
       +                }
       +                HALF_BLOCK_TOP => {
       +                    return AttributedChar::new(HALF_BLOCK_BOTTOM, flip_colors(block.attribute));
       +                }
       +                _ => {}
       +            }
       +        } else if half_block.is_vertically_blocky {
       +            match block.ch {
       +                HALF_BLOCK_LEFT => {
       +                    return AttributedChar::new(HALF_BLOCK_RIGHT, flip_colors(block.attribute));
       +                }
       +                HALF_BLOCK_RIGHT => {
       +                    return AttributedChar::new(HALF_BLOCK_LEFT, flip_colors(block.attribute));
       +                }
       +                _ => {}
       +            }
       +        }
       +    }
       +    block
       +}
 (DIR) diff --git a/src/paint/line.rs b/src/paint/line.rs
       @@ -0,0 +1,53 @@
       +use icy_engine::Position;
       +use icy_engine_egui::BufferView;
       +
       +use super::{plot_point, BrushMode, ColorMode, PointRole};
       +
       +fn get_line_points(from: Position, to: Position) -> Vec<Position> {
       +    let dx = (to.x - from.x).abs();
       +    let sx = if from.x < to.x { 1 } else { -1 };
       +    let dy = (to.y - from.y).abs();
       +    let sy = if from.y < to.y { 1 } else { -1 };
       +
       +    let mut err = if dx > dy { dx } else { -dy } / 2;
       +
       +    let mut result = Vec::new();
       +    let mut cur = from;
       +    loop {
       +        result.push(cur);
       +        if cur == to {
       +            break;
       +        }
       +
       +        let e2 = err;
       +        if e2 > -dx {
       +            err -= dy;
       +            cur.x += sx;
       +        }
       +        if e2 < dy {
       +            err += dx;
       +            cur.y += sy;
       +        }
       +    }
       +    result
       +}
       +
       +pub fn draw_line(
       +    buffer_view: &mut BufferView,
       +    from: impl Into<Position>,
       +    to: impl Into<Position>,
       +    mode: BrushMode,
       +    color_mode: ColorMode,
       +) {
       +    let from = from.into();
       +    let to = to.into();
       +    for point in get_line_points(from, to) {
       +        plot_point(
       +            buffer_view,
       +            point,
       +            mode.clone(),
       +            color_mode,
       +            PointRole::Line,
       +        );
       +    }
       +}
 (DIR) diff --git a/src/paint/mod.rs b/src/paint/mod.rs
       @@ -0,0 +1,203 @@
       +use eframe::egui;
       +use i18n_embed_fl::fl;
       +use icy_engine::{AttributedChar, Position, TextAttribute, TextPane};
       +use icy_engine_egui::BufferView;
       +
       +use crate::{model::brush_imp::draw_glyph, AnsiEditor};
       +
       +use self::half_block::get_half_block;
       +
       +mod half_block;
       +mod rectangle;
       +pub use rectangle::*;
       +mod line;
       +pub use line::*;
       +mod ellipse;
       +pub use ellipse::*;
       +
       +#[derive(Clone, Debug, PartialEq)]
       +pub enum BrushMode {
       +    Block,
       +    HalfBlock,
       +    Char(std::rc::Rc<std::cell::RefCell<char>>),
       +    Shade,
       +    Colorize,
       +}
       +
       +impl BrushMode {
       +    pub fn show_ui(
       +        &mut self,
       +        ui: &mut egui::Ui,
       +        editor_opt: Option<&AnsiEditor>,
       +        char_code: std::rc::Rc<std::cell::RefCell<char>>,
       +    ) {
       +        ui.radio_value(
       +            self,
       +            BrushMode::HalfBlock,
       +            fl!(crate::LANGUAGE_LOADER, "tool-half-block"),
       +        );
       +        ui.radio_value(
       +            self,
       +            BrushMode::Block,
       +            fl!(crate::LANGUAGE_LOADER, "tool-full-block"),
       +        );
       +
       +        ui.horizontal(|ui| {
       +            ui.radio_value(
       +                self,
       +                BrushMode::Char(char_code.clone()),
       +                fl!(crate::LANGUAGE_LOADER, "tool-character"),
       +            );
       +            if let Some(editor) = editor_opt {
       +                draw_glyph(ui, editor, &char_code);
       +            }
       +        });
       +        ui.radio_value(
       +            self,
       +            BrushMode::Shade,
       +            fl!(crate::LANGUAGE_LOADER, "tool-shade"),
       +        );
       +        ui.radio_value(
       +            self,
       +            BrushMode::Colorize,
       +            fl!(crate::LANGUAGE_LOADER, "tool-colorize"),
       +        );
       +    }
       +}
       +
       +#[derive(Clone, Copy, Debug, PartialEq)]
       +pub enum ColorMode {
       +    None,
       +    UseFg,
       +    UseBg,
       +    Both,
       +}
       +
       +impl ColorMode {
       +    pub fn use_fore(&self) -> bool {
       +        matches!(self, ColorMode::UseFg | ColorMode::Both)
       +    }
       +
       +    pub fn use_back(&self) -> bool {
       +        matches!(self, ColorMode::UseBg | ColorMode::Both)
       +    }
       +
       +    pub fn show_ui(&mut self, ui: &mut egui::Ui) {
       +        ui.vertical_centered(|ui| {
       +            ui.horizontal(|ui| {
       +                let mut use_fore = self.use_fore();
       +                let mut use_back = self.use_back();
       +
       +                if ui
       +                    .selectable_label(use_fore, fl!(crate::LANGUAGE_LOADER, "tool-fg"))
       +                    .clicked()
       +                {
       +                    use_fore = !use_fore;
       +                }
       +                if ui
       +                    .selectable_label(use_back, fl!(crate::LANGUAGE_LOADER, "tool-bg"))
       +                    .clicked()
       +                {
       +                    use_back = !use_back;
       +                }
       +
       +                if use_fore && use_back {
       +                    *self = ColorMode::Both;
       +                } else if use_fore {
       +                    *self = ColorMode::UseFg;
       +                } else if use_back {
       +                    *self = ColorMode::UseBg;
       +                } else {
       +                    *self = ColorMode::None;
       +                }
       +            });
       +        });
       +    }
       +}
       +
       +pub fn plot_point(
       +    buffer_view: &mut BufferView,
       +    pos: impl Into<Position>,
       +    mut mode: BrushMode,
       +    color_mode: ColorMode,
       +    point_role: PointRole,
       +) {
       +    let pos = pos.into();
       +    let text_pos = Position::new(pos.x, pos.y / 2);
       +    let mut ch = if let Some(layer) = buffer_view.get_edit_state().get_cur_layer() {
       +        layer.get_char(text_pos)
       +    } else {
       +        return;
       +    };
       +
       +    let editor_attr = buffer_view.get_caret().get_attribute();
       +    let mut attribute = ch.attribute;
       +    if !ch.is_visible() {
       +        attribute = TextAttribute::default();
       +    }
       +    attribute.set_font_page(editor_attr.get_font_page());
       +    if color_mode.use_fore() {
       +        attribute.set_foreground(editor_attr.get_foreground());
       +    }
       +    if color_mode.use_back() {
       +        attribute.set_background(editor_attr.get_background());
       +    }
       +
       +    let Some(layer) = buffer_view.get_buffer_mut().get_overlay_layer() else {
       +        return;
       +    };
       +    let overlay_ch = layer.get_char(text_pos);
       +    if overlay_ch.is_visible() {
       +        ch = overlay_ch;
       +    }
       +
       +    if matches!(mode, BrushMode::HalfBlock) && matches!(point_role, PointRole::Fill) {
       +        mode = BrushMode::Block;
       +    }
       +
       +    match mode {
       +        BrushMode::HalfBlock => {
       +            layer.set_char(
       +                text_pos,
       +                get_half_block(ch, pos, attribute.get_foreground()),
       +            );
       +        }
       +        BrushMode::Block => {
       +            layer.set_char(text_pos, AttributedChar::new(219 as char, attribute));
       +        }
       +        BrushMode::Char(ch) => {
       +            layer.set_char(text_pos, AttributedChar::new(*ch.borrow(), attribute));
       +        }
       +        BrushMode::Shade => {
       +            let mut char_code = SHADE_GRADIENT[0];
       +            if ch.ch == SHADE_GRADIENT[SHADE_GRADIENT.len() - 1] {
       +                char_code = SHADE_GRADIENT[SHADE_GRADIENT.len() - 1];
       +            } else {
       +                for i in 0..SHADE_GRADIENT.len() - 1 {
       +                    if ch.ch == SHADE_GRADIENT[i] {
       +                        char_code = SHADE_GRADIENT[i + 1];
       +                        break;
       +                    }
       +                }
       +            }
       +            layer.set_char(text_pos, AttributedChar::new(char_code, attribute));
       +        }
       +        BrushMode::Colorize => {
       +            layer.set_char(text_pos, AttributedChar::new(ch.ch, attribute));
       +        }
       +    }
       +}
       +
       +pub static SHADE_GRADIENT: [char; 4] = ['\u{00B0}', '\u{00B1}', '\u{00B2}', '\u{00DB}'];
       +pub enum PointRole {
       +    NWCorner,
       +    NECorner,
       +    SWCorner,
       +    SECorner,
       +    LeftSide,
       +    RightSide,
       +    TopSide,
       +    BottomSide,
       +    Fill,
       +    Line,
       +}
 (DIR) diff --git a/src/paint/rectangle.rs b/src/paint/rectangle.rs
       @@ -0,0 +1,102 @@
       +use icy_engine::Position;
       +use icy_engine_egui::BufferView;
       +
       +use super::{plot_point, BrushMode, ColorMode, PointRole};
       +
       +pub fn draw_rectangle(
       +    buffer_view: &mut BufferView,
       +    from: impl Into<Position>,
       +    to: impl Into<Position>,
       +    mode: BrushMode,
       +    color_mode: ColorMode,
       +) {
       +    let from = from.into();
       +    let to = to.into();
       +    plot_point(
       +        buffer_view,
       +        from,
       +        mode.clone(),
       +        color_mode,
       +        PointRole::NWCorner,
       +    );
       +    plot_point(
       +        buffer_view,
       +        (to.x, from.y),
       +        mode.clone(),
       +        color_mode,
       +        PointRole::NECorner,
       +    );
       +
       +    plot_point(
       +        buffer_view,
       +        (from.x, to.y),
       +        mode.clone(),
       +        color_mode,
       +        PointRole::SWCorner,
       +    );
       +    plot_point(
       +        buffer_view,
       +        (to.x, to.y),
       +        mode.clone(),
       +        color_mode,
       +        PointRole::SECorner,
       +    );
       +
       +    for x in from.x + 1..to.x {
       +        plot_point(
       +            buffer_view,
       +            (x, from.y),
       +            mode.clone(),
       +            color_mode,
       +            PointRole::TopSide,
       +        );
       +        plot_point(
       +            buffer_view,
       +            (x, to.y),
       +            mode.clone(),
       +            color_mode,
       +            PointRole::BottomSide,
       +        );
       +    }
       +
       +    for y in from.y + 1..to.y {
       +        plot_point(
       +            buffer_view,
       +            (from.x, y),
       +            mode.clone(),
       +            color_mode,
       +            PointRole::LeftSide,
       +        );
       +        plot_point(
       +            buffer_view,
       +            (to.x, y),
       +            mode.clone(),
       +            color_mode,
       +            PointRole::RightSide,
       +        );
       +    }
       +}
       +
       +pub fn fill_rectangle(
       +    buffer_view: &mut BufferView,
       +    from: impl Into<Position>,
       +    to: impl Into<Position>,
       +    mode: BrushMode,
       +    color_mode: ColorMode,
       +) {
       +    let from = from.into();
       +    let to = to.into();
       +
       +    for y in from.y + 1..to.y {
       +        for x in from.x + 1..to.x {
       +            plot_point(
       +                buffer_view,
       +                (x, y),
       +                mode.clone(),
       +                color_mode,
       +                PointRole::Fill,
       +            );
       +        }
       +    }
       +    draw_rectangle(buffer_view, from, to, mode, color_mode);
       +}
 (DIR) diff --git a/src/ui/editor/ansi/mod.rs b/src/ui/editor/ansi/mod.rs
       @@ -190,6 +190,7 @@ impl Document for AnsiEditor {
                let mut message = None;
        
                let mut scale = options.get_scale();
       +        self.buffer_view.lock().get_caret_mut().is_visible = cur_tool.use_caret();
                if self.buffer_view.lock().get_buffer().use_aspect_ratio() {
                    if self.buffer_view.lock().get_buffer().use_letter_spacing() {
                        scale.y *= 1.2;
 (DIR) diff --git a/src/ui/main_window.rs b/src/ui/main_window.rs
       @@ -1,8 +1,6 @@
        use std::{
       -    cell::RefCell,
            fs,
            path::Path,
       -    rc::Rc,
            sync::{Arc, Mutex},
            time::{Duration, Instant},
        };
       @@ -80,46 +78,11 @@ impl MainWindow {
                    Box::<crate::model::brush_imp::BrushTool>::default(),
                    Box::<crate::model::erase_imp::EraseTool>::default(),
                    Box::<crate::model::pipette_imp::PipetteTool>::default(),
       -            Box::new(crate::model::line_imp::LineTool {
       -                draw_mode: crate::model::DrawMode::Line,
       -                use_fore: true,
       -                use_back: true,
       -                attr: icy_engine::TextAttribute::default(),
       -                char_code: Rc::new(RefCell::new('\u{00B0}')),
       -                old_pos: icy_engine::Position { x: 0, y: 0 },
       -            }),
       -            Box::new(crate::model::draw_rectangle_imp::DrawRectangleTool {
       -                draw_mode: crate::model::DrawMode::Line,
       -                use_fore: true,
       -                use_back: true,
       -                attr: icy_engine::TextAttribute::default(),
       -                char_code: Rc::new(RefCell::new('\u{00B0}')),
       -            }),
       -            Box::new(
       -                crate::model::draw_rectangle_filled_imp::DrawRectangleFilledTool {
       -                    draw_mode: crate::model::DrawMode::Line,
       -                    use_fore: true,
       -                    use_back: true,
       -                    attr: icy_engine::TextAttribute::default(),
       -                    char_code: Rc::new(RefCell::new('\u{00B0}')),
       -                },
       -            ),
       -            Box::new(crate::model::draw_ellipse_imp::DrawEllipseTool {
       -                draw_mode: crate::model::DrawMode::Line,
       -                use_fore: true,
       -                use_back: true,
       -                attr: icy_engine::TextAttribute::default(),
       -                char_code: Rc::new(RefCell::new('\u{00B0}')),
       -            }),
       -            Box::new(
       -                crate::model::draw_ellipse_filled_imp::DrawEllipseFilledTool {
       -                    draw_mode: crate::model::DrawMode::Line,
       -                    use_fore: true,
       -                    use_back: true,
       -                    attr: icy_engine::TextAttribute::default(),
       -                    char_code: Rc::new(RefCell::new('\u{00B0}')),
       -                },
       -            ),
       +            Box::<crate::model::line_imp::LineTool>::default(),
       +            Box::<crate::model::draw_rectangle_imp::DrawRectangleTool>::default(),
       +            Box::<crate::model::draw_rectangle_filled_imp::DrawRectangleFilledTool>::default(),
       +            Box::<crate::model::draw_ellipse_imp::DrawEllipseTool>::default(),
       +            Box::<crate::model::draw_ellipse_filled_imp::DrawEllipseFilledTool>::default(),
                    Box::new(crate::model::fill_imp::FillTool::new()),
                    Box::new(fnt),
                    Box::<crate::model::move_layer_imp::MoveLayer>::default(),