URI: 
       Implemented basic tdf support. - icy_draw - icy_draw is the successor to mystic draw. fork / mirror
  HTML git clone https://git.drkhsh.at/icy_draw.git
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit a2414b526b2a1393decbc70b1761bc5ada7ef3ea
   DIR parent 340063f062165a17b0b808f2cce99524f5474b04
  HTML Author: Mike Krüger <mkrueger@posteo.de>
       Date:   Thu, 31 Aug 2023 23:04:57 +0200
       
       Implemented basic tdf support.
       
       Diffstat:
         M Cargo.toml                          |       9 +++++----
         M i18n/en/icy_draw.ftl                |      11 ++++++++---
         M src/model/tools/brush_imp.rs        |      19 ++++++++-----------
         M src/model/tools/click_imp.rs        |      18 ++++++------------
         M src/model/tools/draw_ellipse_fille… |       9 ++++-----
         M src/model/tools/draw_ellipse_imp.rs |      10 +++++-----
         M src/model/tools/draw_rectangle_fil… |      12 +++++-------
         M src/model/tools/draw_rectangle_imp… |      10 +++++-----
         M src/model/tools/erase_imp.rs        |       8 ++++----
         M src/model/tools/fill_imp.rs         |      17 +++++++++++------
         M src/model/tools/flip_imp.rs         |       8 ++++----
         M src/model/tools/font_imp.rs         |     171 ++++++++++++++++++++++++-------
         M src/model/tools/line_imp.rs         |      12 +++++-------
         M src/model/tools/mod.rs              |      16 ++++------------
         M src/model/tools/move_layer_imp.rs   |       8 ++++----
         M src/model/tools/pencil_imp.rs       |      10 +++++-----
         M src/model/tools/pipette_imp.rs      |       8 ++++----
         M src/ui/dialogs/about_dialog.rs      |       2 +-
         M src/ui/dialogs/edit_layer_dialog.rs |       2 +-
         M src/ui/dialogs/edit_sauce_dialog.rs |       2 +-
         M src/ui/dialogs/mod.rs               |       4 ++++
         M src/ui/dialogs/new_file_dialog.rs   |       2 +-
         M src/ui/dialogs/select_character_di… |       3 +--
         A src/ui/dialogs/select_font_dialog.… |     296 +++++++++++++++++++++++++++++++
         M src/ui/dialogs/select_outline_dial… |       2 +-
         M src/ui/dialogs/set_canvas_size_dia… |       2 +-
         M src/ui/document.rs                  |       1 -
         M src/ui/editor/mod.rs                |      28 ++++++----------------------
         M src/ui/main_window.rs               |      53 +++++++++++++++++++------------
         M src/ui/messages.rs                  |      35 ++++++++++++++++++++++++++++---
         M src/ui/top_bar.rs                   |      16 +++++-----------
       
       31 files changed, 605 insertions(+), 199 deletions(-)
       ---
   DIR diff --git a/Cargo.toml b/Cargo.toml
       @@ -19,11 +19,12 @@ egui_file = "0.10.0"
        egui_tiles = "0.2"
        egui-notify = "0.8.0"
        log = "0.4.20"
       +open = "5.0.0"
        
       -#icy_engine = { git ="https://github.com/mkrueger/icy_engine" }
       -#icy_engine_egui = { git ="https://github.com/mkrueger/icy_engine_egui" }
       -icy_engine = { path = "../icy_engine" }
       -icy_engine_egui = { path = "../icy_engine_egui" }
       +icy_engine = { git ="https://github.com/mkrueger/icy_engine" }
       +icy_engine_egui = { git ="https://github.com/mkrueger/icy_engine_egui" }
       +#icy_engine = { path = "../icy_engine" }
       +#icy_engine_egui = { path = "../icy_engine_egui" }
        walkdir = "2"
        serde = { version = "1", features = ["derive"], optional = true }
        lazy_static = "1.4.0"
   DIR diff --git a/i18n/en/icy_draw.ftl b/i18n/en/icy_draw.ftl
       @@ -105,7 +105,6 @@ about-dialog-description =
            Source code is available at www.github.com/mkrueger/icy_draw
        about-dialog-created_by = Created by { $authors }
        
       -
        edit-layer-dialog-title=Layer properties
        edit-layer-dialog-name-label=Name:
        edit-layer-dialog-is-visible-checkbox=Visible
       @@ -116,5 +115,11 @@ edit-layer-dialog-is-y-offset-label=Y offset:
        edit-layer-dialog-has-alpha-checkbox=Has alpha
        edit-layer-dialog-is-alpha-locked-checkbox=Alpha locked
        
       +error-load-file=Error loading file: { $error }
       +
        
       -error-load-file=Error loading file: { $error }
       -\ No newline at end of file
       +select-font-dialog-title=Select Font ({ $fontcount} available)
       +select-font-dialog-select=Select
       +select-font-dialog-filter-text=Filter fonts
       +select-font-dialog-no-fonts=No fonts matches the filter
       +select-font-dialog-no-fonts-installed=No fonts installed
       +\ No newline at end of file
   DIR diff --git a/src/model/tools/brush_imp.rs b/src/model/tools/brush_imp.rs
       @@ -7,9 +7,9 @@ use i18n_embed_fl::fl;
        use icy_engine::AttributedChar;
        use std::{cell::RefCell, rc::Rc};
        
       -use crate::{AnsiEditor, SelectCharacterDialog};
       +use crate::{AnsiEditor, Message};
        
       -use super::{Position, Tool, ToolUiResult};
       +use super::{Position, Tool};
        
        #[derive(PartialEq, Eq)]
        pub enum BrushType {
       @@ -116,8 +116,8 @@ impl Tool for BrushTool {
                _ctx: &egui::Context,
                ui: &mut egui::Ui,
                buffer_opt: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        let mut result = ToolUiResult::default();
       +    ) -> Option<Message> {
       +        let mut result = None;
                ui.vertical_centered(|ui| {
                    ui.horizontal(|ui| {
                        if ui
       @@ -154,7 +154,7 @@ impl Tool for BrushTool {
                        fl!(crate::LANGUAGE_LOADER, "tool-character"),
                    );
        
       -            draw_glyph(ui, buffer_opt, &mut result, &self.char_code, self.font_page);
       +            result = draw_glyph(ui, buffer_opt, &self.char_code, self.font_page);
                });
                ui.radio_value(
                    &mut self.brush_type,
       @@ -190,10 +190,9 @@ impl Tool for BrushTool {
        pub fn draw_glyph(
            ui: &mut egui::Ui,
            editor: &AnsiEditor,
       -    ui_result: &mut ToolUiResult,
            ch: &Rc<RefCell<char>>,
            font_page: usize,
       -) {
       +) -> Option<Message> {
            if let Some(font) = editor.buffer_view.lock().buf.get_font(font_page) {
                let scale = 1.5;
                let (id, stroke_rect) = ui.allocate_space(Vec2::new(
       @@ -209,10 +208,7 @@ pub fn draw_glyph(
                };
        
                if response.clicked() {
       -            ui_result.modal_dialog = Some(Box::new(SelectCharacterDialog::new(
       -                editor.buffer_view.clone(),
       -                ch.clone(),
       -            )));
       +            return Some(crate::Message::ShowCharacterSelectionDialog(ch.clone()));
                }
        
                let painter = ui.painter_at(stroke_rect);
       @@ -261,4 +257,5 @@ pub fn draw_glyph(
                    });
                }
            }
       +    None
        }
   DIR diff --git a/src/model/tools/click_imp.rs b/src/model/tools/click_imp.rs
       @@ -2,9 +2,9 @@ use eframe::egui;
        use egui_extras::RetainedImage;
        use icy_engine::Selection;
        
       -use crate::AnsiEditor;
       +use crate::{AnsiEditor, Message};
        
       -use super::{Event, Position, Tool, ToolUiResult};
       +use super::{Event, Position, Tool};
        
        pub struct ClickTool {}
        
       @@ -18,17 +18,14 @@ impl Tool for ClickTool {
                _ctx: &egui::Context,
                _ui: &mut egui::Ui,
                _buffer_opt: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        ToolUiResult::default()
       +    ) -> Option<Message> {
       +        None
            }
        
            fn handle_click(&mut self, editor: &mut AnsiEditor, button: i32, pos: Position) -> Event {
                if button == 1 {
                    editor.set_caret_position(pos);
       -            editor
       -                .buffer_view
       -                .lock()
       -                .clear_selection();
       +            editor.buffer_view.lock().clear_selection();
                }
                Event::None
            }
       @@ -62,10 +59,7 @@ impl Tool for ClickTool {
                }
        
                if start == cur {
       -            editor
       -                .buffer_view
       -                .lock()
       -                .clear_selection();
       +            editor.buffer_view.lock().clear_selection();
                }
        
                Event::None
   DIR diff --git a/src/model/tools/draw_ellipse_filled_imp.rs b/src/model/tools/draw_ellipse_filled_imp.rs
       @@ -2,11 +2,10 @@ use eframe::egui;
        use i18n_embed_fl::fl;
        use icy_engine::{Rectangle, TextAttribute};
        
       -use crate::AnsiEditor;
       +use crate::{AnsiEditor, Message};
        
        use super::{
            brush_imp::draw_glyph, plot_point, DrawMode, Event, Plottable, Position, ScanLines, Tool,
       -    ToolUiResult,
        };
        
        pub struct DrawEllipseFilledTool {
       @@ -51,8 +50,8 @@ impl Tool for DrawEllipseFilledTool {
                _ctx: &egui::Context,
                ui: &mut egui::Ui,
                editor: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        let mut result = ToolUiResult::default();
       +    ) -> Option<Message> {
       +        let mut result = None;
                ui.vertical_centered(|ui| {
                    ui.horizontal(|ui| {
                        if ui
       @@ -82,7 +81,7 @@ impl Tool for DrawEllipseFilledTool {
                        fl!(crate::LANGUAGE_LOADER, "tool-character"),
                    );
        
       -            draw_glyph(ui, editor, &mut result, &self.char_code, self.font_page);
       +            result = draw_glyph(ui, editor, &self.char_code, self.font_page);
                });
                ui.radio_value(
                    &mut self.draw_mode,
   DIR diff --git a/src/model/tools/draw_ellipse_imp.rs b/src/model/tools/draw_ellipse_imp.rs
       @@ -2,11 +2,11 @@ use eframe::egui;
        use i18n_embed_fl::fl;
        use icy_engine::{Rectangle, TextAttribute};
        
       -use crate::AnsiEditor;
       +use crate::{AnsiEditor, Message};
        
        use super::{
            brush_imp::draw_glyph, line_imp::set_half_block, DrawMode, Event, Plottable, Position,
       -    ScanLines, Tool, ToolUiResult,
       +    ScanLines, Tool,
        };
        
        pub struct DrawEllipseTool {
       @@ -51,8 +51,8 @@ impl Tool for DrawEllipseTool {
                _ctx: &egui::Context,
                ui: &mut egui::Ui,
                editor: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        let mut result = ToolUiResult::default();
       +    ) -> Option<Message> {
       +        let mut result = None;
                ui.vertical_centered(|ui| {
                    ui.horizontal(|ui| {
                        if ui
       @@ -82,7 +82,7 @@ impl Tool for DrawEllipseTool {
                        fl!(crate::LANGUAGE_LOADER, "tool-character"),
                    );
        
       -            draw_glyph(ui, editor, &mut result, &self.char_code, self.font_page);
       +            result = draw_glyph(ui, editor, &self.char_code, self.font_page);
                });
                ui.radio_value(
                    &mut self.draw_mode,
   DIR diff --git a/src/model/tools/draw_rectangle_filled_imp.rs b/src/model/tools/draw_rectangle_filled_imp.rs
       @@ -2,11 +2,9 @@ use eframe::egui;
        use i18n_embed_fl::fl;
        use icy_engine::{Position, Rectangle, TextAttribute};
        
       -use crate::AnsiEditor;
       +use crate::{AnsiEditor, Message};
        
       -use super::{
       -    brush_imp::draw_glyph, plot_point, DrawMode, Event, Plottable, ScanLines, Tool, ToolUiResult,
       -};
       +use super::{brush_imp::draw_glyph, plot_point, DrawMode, Event, Plottable, ScanLines, Tool};
        
        pub struct DrawRectangleFilledTool {
            pub draw_mode: DrawMode,
       @@ -51,8 +49,8 @@ impl Tool for DrawRectangleFilledTool {
                _ctx: &egui::Context,
                ui: &mut egui::Ui,
                editor: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        let mut result = ToolUiResult::default();
       +    ) -> Option<Message> {
       +        let mut result = None;
                ui.vertical_centered(|ui| {
                    ui.horizontal(|ui| {
                        if ui
       @@ -82,7 +80,7 @@ impl Tool for DrawRectangleFilledTool {
                        fl!(crate::LANGUAGE_LOADER, "tool-character"),
                    );
        
       -            draw_glyph(ui, editor, &mut result, &self.char_code, self.font_page);
       +            result = draw_glyph(ui, editor, &self.char_code, self.font_page);
                });
                ui.radio_value(
                    &mut self.draw_mode,
   DIR diff --git a/src/model/tools/draw_rectangle_imp.rs b/src/model/tools/draw_rectangle_imp.rs
       @@ -2,11 +2,11 @@ use eframe::egui;
        use i18n_embed_fl::fl;
        use icy_engine::{Rectangle, TextAttribute};
        
       -use crate::AnsiEditor;
       +use crate::{AnsiEditor, Message};
        
        use super::{
            brush_imp::draw_glyph, line_imp::set_half_block, DrawMode, Event, Plottable, Position,
       -    ScanLines, Tool, ToolUiResult,
       +    ScanLines, Tool,
        };
        
        pub struct DrawRectangleTool {
       @@ -51,8 +51,8 @@ impl Tool for DrawRectangleTool {
                _ctx: &egui::Context,
                ui: &mut egui::Ui,
                editor: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        let mut result = ToolUiResult::default();
       +    ) -> Option<Message> {
       +        let mut result = None;
                ui.vertical_centered(|ui| {
                    ui.horizontal(|ui| {
                        if ui
       @@ -82,7 +82,7 @@ impl Tool for DrawRectangleTool {
                        fl!(crate::LANGUAGE_LOADER, "tool-character"),
                    );
        
       -            draw_glyph(ui, editor, &mut result, &self.char_code, self.font_page);
       +            result = draw_glyph(ui, editor, &self.char_code, self.font_page);
                });
                ui.radio_value(
                    &mut self.draw_mode,
   DIR diff --git a/src/model/tools/erase_imp.rs b/src/model/tools/erase_imp.rs
       @@ -2,9 +2,9 @@ use eframe::egui;
        use i18n_embed_fl::fl;
        use icy_engine::{AttributedChar, TextAttribute};
        
       -use crate::AnsiEditor;
       +use crate::{AnsiEditor, Message};
        
       -use super::{Position, Tool, ToolUiResult};
       +use super::{Position, Tool};
        
        #[derive(PartialEq, Eq)]
        pub enum EraseType {
       @@ -83,7 +83,7 @@ impl Tool for EraseTool {
                _ctx: &egui::Context,
                ui: &mut egui::Ui,
                _buffer_opt: &AnsiEditor,
       -    ) -> ToolUiResult {
       +    ) -> Option<Message> {
                ui.horizontal(|ui| {
                    ui.label(fl!(crate::LANGUAGE_LOADER, "tool-size-label"));
                    ui.add(
       @@ -102,7 +102,7 @@ impl Tool for EraseTool {
                    EraseType::Shade,
                    fl!(crate::LANGUAGE_LOADER, "tool-shade"),
                );
       -        ToolUiResult::default()
       +        None
            }
        
            fn handle_click(
   DIR diff --git a/src/model/tools/fill_imp.rs b/src/model/tools/fill_imp.rs
       @@ -4,9 +4,9 @@ use eframe::egui;
        use i18n_embed_fl::fl;
        use icy_engine::{AttributedChar, TextAttribute};
        
       -use crate::AnsiEditor;
       +use crate::{AnsiEditor, Message};
        
       -use super::{brush_imp::draw_glyph, Event, Position, Tool, ToolUiResult};
       +use super::{brush_imp::draw_glyph, Event, Position, Tool};
        
        #[derive(PartialEq, Eq)]
        pub enum FillType {
       @@ -33,7 +33,12 @@ impl FillTool {
                old_ch: AttributedChar,
                new_ch: AttributedChar,
            ) {
       -        if pos.x < 0 || pos.y < 0 || pos.x >= editor.buffer_view.lock().buf.get_width() as i32  || pos.x >= editor.buffer_view.lock().buf.get_height() as i32 || !visited.insert(pos) {
       +        if pos.x < 0
       +            || pos.y < 0
       +            || pos.x >= editor.buffer_view.lock().buf.get_width() as i32
       +            || pos.x >= editor.buffer_view.lock().buf.get_height() as i32
       +            || !visited.insert(pos)
       +        {
                    return;
                }
        
       @@ -122,8 +127,8 @@ impl Tool for FillTool {
                _ctx: &egui::Context,
                ui: &mut egui::Ui,
                editor: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        let mut result = ToolUiResult::default();
       +    ) -> Option<Message> {
       +        let mut result = None;
                ui.vertical_centered(|ui| {
                    ui.horizontal(|ui| {
                        if ui
       @@ -148,7 +153,7 @@ impl Tool for FillTool {
                        fl!(crate::LANGUAGE_LOADER, "tool-character"),
                    );
        
       -            draw_glyph(ui, editor, &mut result, &self.char_code, self.font_page);
       +            result = draw_glyph(ui, editor, &self.char_code, self.font_page);
                });
                ui.radio_value(
                    &mut self.fill_type,
   DIR diff --git a/src/model/tools/flip_imp.rs b/src/model/tools/flip_imp.rs
       @@ -1,8 +1,8 @@
        use eframe::egui;
        
       -use crate::AnsiEditor;
       +use crate::{AnsiEditor, Message};
        
       -use super::{Event, Position, Tool, ToolUiResult};
       +use super::{Event, Position, Tool};
        pub struct FlipTool {}
        
        impl Tool for FlipTool {
       @@ -20,8 +20,8 @@ impl Tool for FlipTool {
                _ctx: &egui::Context,
                _ui: &mut egui::Ui,
                _buffer_opt: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        ToolUiResult::default()
       +    ) -> Option<Message> {
       +        None
            }
        
            fn handle_click(&mut self, editor: &mut AnsiEditor, button: i32, pos: Position) -> Event {
   DIR diff --git a/src/model/tools/font_imp.rs b/src/model/tools/font_imp.rs
       @@ -1,18 +1,21 @@
       -use std::fs;
       +use std::{
       +    fs,
       +    sync::{Arc, Mutex},
       +};
        
       -use crate::{AnsiEditor, SETTINGS};
       +use crate::{AnsiEditor, Message, SETTINGS};
        
       -use super::{Event, MKey, MModifiers, Position, Tool, ToolUiResult};
       +use super::{Event, MKey, MModifiers, Position, Tool};
        use directories::ProjectDirs;
        use eframe::{
       -    egui::{self, ComboBox},
       -    epaint::{text::LayoutJob, Color32, FontId},
       +    egui::{self, RichText},
       +    epaint::{FontFamily, FontId},
        };
        use icy_engine::{Rectangle, Size, TextAttribute, TheDrawFont};
        use walkdir::{DirEntry, WalkDir};
        pub struct FontTool {
       -    pub selected_font: i32,
       -    pub fonts: Vec<TheDrawFont>,
       +    pub selected_font: Arc<Mutex<i32>>,
       +    pub fonts: Arc<Mutex<Vec<TheDrawFont>>>,
            pub sizes: Vec<Size>,
        }
        
       @@ -39,7 +42,7 @@ impl FontTool {
                            )
                        });
                    }
       -            self.fonts.clear();
       +            let mut fonts = Vec::new();
                    let walker = WalkDir::new(tdf_dir).into_iter();
                    for entry in walker.filter_entry(|e| !FontTool::is_hidden(e)) {
                        if let Err(e) = entry {
       @@ -64,10 +67,12 @@ impl FontTool {
        
                        if extension == "tdf" {
                            if let Some(font) = TheDrawFont::load(path) {
       -                        self.fonts.push(font);
       +                        fonts.push(font);
                            }
                        }
                    }
       +
       +            self.fonts = Arc::new(Mutex::new(fonts));
                }
            }
        }
       @@ -85,49 +90,148 @@ impl Tool for FontTool {
                _ctx: &egui::Context,
                ui: &mut egui::Ui,
                _buffer_opt: &AnsiEditor,
       -    ) -> ToolUiResult {
       +    ) -> Option<Message> {
       +        let mut select = false;
       +        let font_count = self.fonts.lock().unwrap().len();
       +        let selected_font = *self.selected_font.lock().unwrap();
       +
                ui.vertical_centered(|ui| {
       +            ui.label("Selected font");
       +
                    let mut selected_text = "<none>".to_string();
        
       -            if self.selected_font >= 0 && (self.selected_font as usize) < self.fonts.len() {
       -                if let Some(font) = self.fonts.get(self.selected_font as usize) {
       +            if selected_font >= 0 && (selected_font as usize) < font_count {
       +                if let Some(font) = self.fonts.lock().unwrap().get(selected_font as usize) {
                            selected_text = font.name.clone();
                        }
                    }
       +            let selected_text =
       +                RichText::new(selected_text).font(FontId::new(18.0, FontFamily::Proportional));
       +            select = ui.button(selected_text).clicked();
       +        });
       +
       +        ui.add_space(8.0);
       +
       +        ui.vertical_centered(|ui| {
       +            ui.horizontal(|ui| {
       +                if let Some(font) = self.fonts.lock().unwrap().get(selected_font as usize) {
       +                    for ch in '!'..'@' {
       +                        ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
       +                        let color = if font.has_char(ch as u8) {
       +                            ui.style().visuals.strong_text_color()
       +                        } else {
       +                            ui.style().visuals.text_color()
       +                        };
        
       -            ComboBox::from_label("Font")
       -                .wrap(false)
       -                .selected_text(selected_text)
       -                .show_ui(ui, |ui| {
       -                    for i in 0..self.fonts.len() {
       -                        let text = LayoutJob::simple_singleline(
       -                            self.fonts[i].name.clone(),
       -                            FontId::default(),
       -                            Color32::WHITE,
       +                        ui.colored_label(
       +                            color,
       +                            RichText::new(ch.to_string())
       +                                .font(FontId::new(12.0, FontFamily::Monospace)),
                                );
       -                        ui.selectable_value(&mut self.selected_font, i as i32, text);
                            }
       -                });
       +                }
       +            });
       +
       +            ui.horizontal(|ui| {
       +                if let Some(font) = self.fonts.lock().unwrap().get(selected_font as usize) {
       +                    for ch in '@'..'_' {
       +                        ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
       +                        let color = if font.has_char(ch as u8) {
       +                            ui.style().visuals.strong_text_color()
       +                        } else {
       +                            ui.style().visuals.text_color()
       +                        };
       +
       +                        ui.colored_label(
       +                            color,
       +                            RichText::new(ch.to_string())
       +                                .font(FontId::new(12.0, FontFamily::Monospace)),
       +                        );
       +                    }
       +                }
       +            });
       +
       +            ui.horizontal(|ui| {
       +                if let Some(font) = self.fonts.lock().unwrap().get(selected_font as usize) {
       +                    ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
       +                    for ch in '_'..'~' {
       +                        let color = if font.has_char(ch as u8) {
       +                            ui.style().visuals.strong_text_color()
       +                        } else {
       +                            ui.style().visuals.text_color()
       +                        };
       +
       +                        ui.colored_label(
       +                            color,
       +                            RichText::new(ch.to_string())
       +                                .font(FontId::new(12.0, FontFamily::Monospace)),
       +                        );
       +                    }
       +                }
       +            });
       +            ui.horizontal(|ui| {
       +                if let Some(font) = self.fonts.lock().unwrap().get(selected_font as usize) {
       +                    ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
       +                    for ch in '~'..='~' {
       +                        let color = if font.has_char(ch as u8) {
       +                            ui.style().visuals.strong_text_color()
       +                        } else {
       +                            ui.style().visuals.text_color()
       +                        };
       +
       +                        ui.colored_label(
       +                            color,
       +                            RichText::new(ch.to_string())
       +                                .font(FontId::new(12.0, FontFamily::Monospace)),
       +                        );
       +                    }
       +                }
       +            });
                });
       -        ToolUiResult::default()
       +        ui.add_space(32.0);
       +        if ui.button("Select Font Outline").clicked() {
       +            return Some(Message::ShowOutlineDialog);
       +        }
       +
       +        ui.add_space(32.0);
       +        ui.label("Install new fonts in the font directory.");
       +        if ui.button("Open font directory").clicked() {
       +            if let Some(proj_dirs) = ProjectDirs::from("com", "GitHub", "icy_draw") {
       +                let tdf_dir = proj_dirs.config_dir().join("tdf");
       +                if let Err(err) = open::that(tdf_dir) {
       +                    return Some(Message::ShowError(format!(
       +                        "Can't open font directory: {}",
       +                        err
       +                    )));
       +                }
       +            }
       +        }
       +
       +        if select {
       +            Some(Message::SelectFontDialog(
       +                self.fonts.clone(),
       +                self.selected_font.clone(),
       +            ))
       +        } else {
       +            None
       +        }
            }
        
            fn handle_click(&mut self, editor: &mut AnsiEditor, button: i32, pos: Position) -> Event {
                if button == 1 {
                    editor.set_caret_position(pos);
       -            editor
       -                .buffer_view
       -                .lock()
       -                .clear_selection();
       +            editor.buffer_view.lock().clear_selection();
                }
                Event::None
            }
        
            fn handle_key(&mut self, editor: &mut AnsiEditor, key: MKey, modifier: MModifiers) -> Event {
       -        if self.selected_font < 0 || self.selected_font >= self.fonts.len() as i32 {
       +        let selected_font = *self.selected_font.lock().unwrap();
       +
       +        if selected_font < 0 || selected_font >= self.fonts.lock().unwrap().len() as i32 {
                    return Event::None;
                }
       -        let font = &self.fonts[self.selected_font as usize];
       +        let font = &self.fonts.lock().unwrap()[selected_font as usize];
                let pos = editor.buffer_view.lock().caret.get_position();
        
                match key {
       @@ -190,10 +294,7 @@ impl Tool for FontTool {
        
                    MKey::Backspace => {
                        let letter_size = self.sizes.pop().unwrap_or_else(|| Size::new(1, 1));
       -                editor
       -                .buffer_view
       -                .lock()
       -                .clear_selection();
       +                editor.buffer_view.lock().clear_selection();
                        let pos = editor.get_caret_position();
                        if pos.x > 0 {
                            editor.set_caret_position(pos + Position::new(-(letter_size.width as i32), 0));
       @@ -236,7 +337,7 @@ impl Tool for FontTool {
                        let c_pos = editor.get_caret_position();
                        editor.begin_atomic_undo();
                        let attr = editor.buffer_view.lock().caret.get_attribute();
       -                let opt_size = font.render(
       +                let opt_size: Option<Size> = font.render(
                            &mut editor.buffer_view.lock().buf,
                            0,
                            c_pos.as_uposition(),
   DIR diff --git a/src/model/tools/line_imp.rs b/src/model/tools/line_imp.rs
       @@ -2,11 +2,9 @@ use eframe::egui;
        use i18n_embed_fl::fl;
        use icy_engine::{AttributedChar, Rectangle, TextAttribute};
        
       -use crate::{model::ScanLines, AnsiEditor};
       +use crate::{model::ScanLines, AnsiEditor, Message};
        
       -use super::{
       -    brush_imp::draw_glyph, plot_point, DrawMode, Event, Plottable, Position, Tool, ToolUiResult,
       -};
       +use super::{brush_imp::draw_glyph, plot_point, DrawMode, Event, Plottable, Position, Tool};
        
        pub struct LineTool {
            pub draw_mode: DrawMode,
       @@ -194,8 +192,8 @@ impl Tool for LineTool {
                _ctx: &egui::Context,
                ui: &mut egui::Ui,
                editor: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        let mut result = ToolUiResult::default();
       +    ) -> Option<Message> {
       +        let mut result = None;
                ui.vertical_centered(|ui| {
                    ui.horizontal(|ui| {
                        if ui
       @@ -225,7 +223,7 @@ impl Tool for LineTool {
                        fl!(crate::LANGUAGE_LOADER, "tool-character"),
                    );
        
       -            draw_glyph(ui, editor, &mut result, &self.char_code, self.font_page);
       +            result = draw_glyph(ui, editor, &self.char_code, self.font_page);
                });
                ui.radio_value(
                    &mut self.draw_mode,
   DIR diff --git a/src/model/tools/mod.rs b/src/model/tools/mod.rs
       @@ -20,7 +20,7 @@ use egui_extras::RetainedImage;
        use icy_engine::{AttributedChar, Position, TextAttribute};
        pub use scan_lines::*;
        
       -use crate::{AnsiEditor, Event};
       +use crate::{AnsiEditor, Event, Message};
        pub mod scan_lines;
        
        #[derive(Copy, Clone, Debug)]
       @@ -76,11 +76,6 @@ impl MModifiers {
            }
        }
        
       -#[derive(Default)]
       -pub struct ToolUiResult {
       -    pub modal_dialog: Option<Box<dyn crate::ModalDialog>>,
       -}
       -
        pub trait Tool {
            fn get_icon_name(&self) -> &'static RetainedImage;
        
       @@ -97,7 +92,7 @@ pub trait Tool {
                ctx: &egui::Context,
                ui: &mut egui::Ui,
                buffer_opt: &AnsiEditor,
       -    ) -> ToolUiResult;
       +    ) -> Option<Message>;
        
            fn handle_key(&mut self, editor: &mut AnsiEditor, key: MKey, modifier: MModifiers) -> Event {
                // TODO Keys:
       @@ -389,11 +384,8 @@ fn handle_outline_insertion(editor: &mut AnsiEditor, modifier: MModifiers, outli
                    return;
                }
            }
       -    editor
       -    .buffer_view
       -    .lock()
       -    .clear_selection();
       -let ch = editor.get_outline_char_code(outline);
       +    editor.buffer_view.lock().clear_selection();
       +    let ch = editor.get_outline_char_code(outline);
            if let Ok(ch) = ch {
                editor.type_key(unsafe { char::from_u32_unchecked(ch as u32) });
            }
   DIR diff --git a/src/model/tools/move_layer_imp.rs b/src/model/tools/move_layer_imp.rs
       @@ -1,5 +1,5 @@
       -use super::{Event, Position, Tool, ToolUiResult};
       -use crate::AnsiEditor;
       +use super::{Event, Position, Tool};
       +use crate::{AnsiEditor, Message};
        use eframe::egui;
        
        pub struct MoveLayer {
       @@ -21,8 +21,8 @@ impl Tool for MoveLayer {
                _ctx: &egui::Context,
                _ui: &mut egui::Ui,
                _buffer_opt: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        ToolUiResult::default()
       +    ) -> Option<Message> {
       +        None
            }
        
            fn handle_drag_begin(
   DIR diff --git a/src/model/tools/pencil_imp.rs b/src/model/tools/pencil_imp.rs
       @@ -6,9 +6,9 @@ use egui_extras::RetainedImage;
        use i18n_embed_fl::fl;
        use icy_engine::{AttributedChar, Rectangle};
        
       -use crate::{model::ScanLines, AnsiEditor};
       +use crate::{model::ScanLines, AnsiEditor, Message};
        
       -use super::{brush_imp::draw_glyph, line_imp::set_half_block, Position, Tool, ToolUiResult};
       +use super::{brush_imp::draw_glyph, line_imp::set_half_block, Position, Tool};
        
        #[derive(PartialEq, Eq)]
        pub enum PencilType {
       @@ -125,8 +125,8 @@ impl Tool for PencilTool {
                _ctx: &egui::Context,
                ui: &mut egui::Ui,
                buffer_opt: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        let mut result = ToolUiResult::default();
       +    ) -> Option<Message> {
       +        let mut result = None;
                ui.vertical_centered(|ui| {
                    ui.horizontal(|ui| {
                        if ui
       @@ -160,7 +160,7 @@ impl Tool for PencilTool {
                        fl!(crate::LANGUAGE_LOADER, "tool-character"),
                    );
        
       -            draw_glyph(ui, buffer_opt, &mut result, &self.char_code, self.font_page);
       +            result = draw_glyph(ui, buffer_opt, &self.char_code, self.font_page);
                });
                ui.radio_value(
                    &mut self.brush_type,
   DIR diff --git a/src/model/tools/pipette_imp.rs b/src/model/tools/pipette_imp.rs
       @@ -1,8 +1,8 @@
        use eframe::egui;
        
       -use crate::AnsiEditor;
       +use crate::{AnsiEditor, Message};
        
       -use super::{Event, Position, Tool, ToolUiResult};
       +use super::{Event, Position, Tool};
        pub struct PipetteTool {}
        
        impl Tool for PipetteTool {
       @@ -20,8 +20,8 @@ impl Tool for PipetteTool {
                _ctx: &egui::Context,
                _ui: &mut egui::Ui,
                _buffer_opt: &AnsiEditor,
       -    ) -> ToolUiResult {
       -        ToolUiResult::default()
       +    ) -> Option<Message> {
       +        None
            }
        
            fn handle_click(&mut self, editor: &mut AnsiEditor, button: i32, pos: Position) -> Event {
   DIR diff --git a/src/ui/dialogs/about_dialog.rs b/src/ui/dialogs/about_dialog.rs
       @@ -12,7 +12,7 @@ pub struct AboutDialog {
        impl crate::ModalDialog for AboutDialog {
            fn show(&mut self, ctx: &egui::Context) -> bool {
                let mut result = false;
       -        let modal = Modal::new(ctx, "my_modal");
       +        let modal = Modal::new(ctx, "help_dialog");
        
                modal.show(|ui| {
                    modal.title(ui, fl!(crate::LANGUAGE_LOADER, "about-dialog-title"));
   DIR diff --git a/src/ui/dialogs/edit_layer_dialog.rs b/src/ui/dialogs/edit_layer_dialog.rs
       @@ -48,7 +48,7 @@ impl EditLayerDialog {
        impl ModalDialog for EditLayerDialog {
            fn show(&mut self, ctx: &egui::Context) -> bool {
                let mut result = false;
       -        let modal: Modal = Modal::new(ctx, "my_modal");
       +        let modal: Modal = Modal::new(ctx, "edit_layer_dialog");
        
                modal.show(|ui| {
                    modal.title(ui, fl!(crate::LANGUAGE_LOADER, "edit-layer-dialog-title"));
   DIR diff --git a/src/ui/dialogs/edit_sauce_dialog.rs b/src/ui/dialogs/edit_sauce_dialog.rs
       @@ -28,7 +28,7 @@ impl EditSauceDialog {
        impl ModalDialog for EditSauceDialog {
            fn show(&mut self, ctx: &egui::Context) -> bool {
                let mut result = false;
       -        let modal: Modal = Modal::new(ctx, "my_modal");
       +        let modal: Modal = Modal::new(ctx, "edit_sauce_dialog");
        
                modal.show(|ui| {
                    modal.title(ui, fl!(crate::LANGUAGE_LOADER, "edit-sauce-title"));
   DIR diff --git a/src/ui/dialogs/mod.rs b/src/ui/dialogs/mod.rs
       @@ -24,5 +24,9 @@ pub use edit_layer_dialog::*;
        
        mod open_file_dialog;
        pub use open_file_dialog::*;
       +
        mod save_file_dialog;
        pub use save_file_dialog::*;
       +
       +mod select_font_dialog;
       +pub use select_font_dialog::*;
   DIR diff --git a/src/ui/dialogs/new_file_dialog.rs b/src/ui/dialogs/new_file_dialog.rs
       @@ -25,7 +25,7 @@ impl Default for NewFileDialog {
        impl crate::ModalDialog for NewFileDialog {
            fn show(&mut self, ctx: &egui::Context) -> bool {
                let mut result = false;
       -        let modal = Modal::new(ctx, "my_modal");
       +        let modal = Modal::new(ctx, "new_file_dialog");
        
                modal.show(|ui| {
                    modal.title(ui, fl!(crate::LANGUAGE_LOADER, "new-file-title"));
   DIR diff --git a/src/ui/dialogs/select_character_dialog.rs b/src/ui/dialogs/select_character_dialog.rs
       @@ -33,7 +33,7 @@ impl ModalDialog for SelectCharacterDialog {
            fn show(&mut self, ctx: &egui::Context) -> bool {
                let font_page = 0;
                let mut result = false;
       -        let modal = Modal::new(ctx, "my_modal");
       +        let modal = Modal::new(ctx, "select_character_dialog");
        
                modal.show(|ui| {
                    modal.title(ui, fl!(crate::LANGUAGE_LOADER, "select-character-title"));
       @@ -73,7 +73,6 @@ impl ModalDialog for SelectCharacterDialog {
                            }
                        }
                    });
       -            //   });
        
                    ui.horizontal(|ui| {
                        ui.label(RichText::new(fl!(crate::LANGUAGE_LOADER, "glyph-char-label")).small());
   DIR diff --git a/src/ui/dialogs/select_font_dialog.rs b/src/ui/dialogs/select_font_dialog.rs
       @@ -0,0 +1,296 @@
       +use std::sync::{Arc, Mutex};
       +
       +use eframe::{
       +    egui::{self, RichText, TextEdit},
       +    epaint::{ahash::HashMap, ColorImage, FontFamily, FontId, Stroke},
       +};
       +use egui_extras::RetainedImage;
       +use egui_modal::Modal;
       +use i18n_embed_fl::fl;
       +use icy_engine::{Buffer, Size, TextAttribute, TheDrawFont, UPosition};
       +
       +use crate::MainWindow;
       +
       +pub struct SelectFontDialog {
       +    fonts: Arc<Mutex<Vec<TheDrawFont>>>,
       +    selected_font_arc: Arc<Mutex<i32>>,
       +    selected_font: i32,
       +    pub do_select: bool,
       +    filter: String,
       +    show_outline: bool,
       +    show_color: bool,
       +    show_block: bool,
       +
       +    image_cache: HashMap<usize, RetainedImage>,
       +}
       +
       +impl SelectFontDialog {
       +    pub fn new(fonts: Arc<Mutex<Vec<TheDrawFont>>>, selected_font_arc: Arc<Mutex<i32>>) -> Self {
       +        let selected_font = *selected_font_arc.lock().unwrap();
       +
       +        Self {
       +            do_select: false,
       +            fonts,
       +            selected_font_arc,
       +            selected_font,
       +            filter: String::new(),
       +            show_outline: true,
       +            show_color: true,
       +            show_block: true,
       +            image_cache: HashMap::default(),
       +        }
       +    }
       +}
       +
       +impl crate::ModalDialog for SelectFontDialog {
       +    fn show(&mut self, ctx: &egui::Context) -> bool {
       +        let mut result = false;
       +        let modal = Modal::new(ctx, "select_font_dialog");
       +        let font_count = self.fonts.lock().unwrap().len();
       +        modal.show(|ui| {
       +            modal.title(
       +                ui,
       +                fl!(
       +                    crate::LANGUAGE_LOADER,
       +                    "select-font-dialog-title",
       +                    fontcount = font_count
       +                ),
       +            );
       +            modal.frame(ui, |ui| {
       +                let row_height = 176.0 / 2.0;
       +                ui.horizontal(|ui: &mut egui::Ui| {
       +                    ui.add_sized(
       +                        [250.0, 20.0],
       +                        TextEdit::singleline(&mut self.filter).hint_text(fl!(
       +                            crate::LANGUAGE_LOADER,
       +                            "select-font-dialog-filter-text"
       +                        )),
       +                    );
       +                    let response = ui.button("🗙");
       +                    if response.clicked() {
       +                        self.filter.clear();
       +                    }
       +
       +                    let response = ui.selectable_label(self.show_color, "COLOR");
       +                    if response.clicked() {
       +                        self.show_color = !self.show_color;
       +                    }
       +
       +                    let response = ui.selectable_label(self.show_block, "BLOCK");
       +                    if response.clicked() {
       +                        self.show_block = !self.show_block;
       +                    }
       +
       +                    let response = ui.selectable_label(self.show_outline, "OUTLINE");
       +                    if response.clicked() {
       +                        self.show_outline = !self.show_outline;
       +                    }
       +                });
       +                ui.add_space(4.0);
       +
       +                let mut filtered_fonts = Vec::new();
       +
       +                for i in 0..font_count {
       +                    let font = &self.fonts.lock().unwrap()[i];
       +                    if font
       +                        .name
       +                        .to_lowercase()
       +                        .contains(&self.filter.to_lowercase())
       +                        && (self.show_outline
       +                            && matches!(font.font_type, icy_engine::TheDrawFontType::Outline)
       +                            || self.show_block
       +                                && matches!(font.font_type, icy_engine::TheDrawFontType::Block)
       +                            || self.show_color
       +                                && matches!(font.font_type, icy_engine::TheDrawFontType::Color))
       +                    {
       +                        filtered_fonts.push(i);
       +                    }
       +                }
       +                if filtered_fonts.is_empty() {
       +                    if font_count == 0 {
       +                        ui.label(fl!(
       +                            crate::LANGUAGE_LOADER,
       +                            "select-font-dialog-no-fonts-installed"
       +                        ));
       +                    } else {
       +                        ui.label(fl!(crate::LANGUAGE_LOADER, "select-font-dialog-no-fonts"));
       +                    }
       +                } else {
       +                    egui::ScrollArea::vertical().max_height(300.).show_rows(
       +                        ui,
       +                        row_height,
       +                        filtered_fonts.len(),
       +                        |ui, range| {
       +                            for row in range {
       +                                let font = &self.fonts.lock().unwrap()[filtered_fonts[row]];
       +                                ui.horizontal(|ui: &mut egui::Ui| {
       +                                    ui.vertical(|ui| {
       +                                        ui.horizontal(|ui| {
       +                                            let font_type = match font.font_type {
       +                                                icy_engine::TheDrawFontType::Outline => "OUTLINE",
       +                                                icy_engine::TheDrawFontType::Block => "BLOCK",
       +                                                icy_engine::TheDrawFontType::Color => "COLOR",
       +                                            };
       +                                            let response = ui.label(font_type);
       +                                            ui.painter().rect_stroke(
       +                                                response.rect.expand(2.0),
       +                                                4.0,
       +                                                Stroke::new(1.0, ctx.style().visuals.text_color()),
       +                                            );
       +
       +                                            let sel = ui.selectable_label(
       +                                                self.selected_font == filtered_fonts[row] as i32,
       +                                                font.name.clone(),
       +                                            );
       +                                            if sel.clicked() {
       +                                                self.selected_font = filtered_fonts[row] as i32;
       +                                            }
       +                                            if sel.double_clicked() {
       +                                                self.selected_font = filtered_fonts[row] as i32;
       +                                                self.do_select = true;
       +                                                result = true;
       +                                            }
       +                                        });
       +                                        ui.horizontal(|ui| {
       +                                            for ch in '!'..'P' {
       +                                                ui.spacing_mut().item_spacing =
       +                                                    eframe::epaint::Vec2::new(0.0, 0.0);
       +                                                let color = if font.has_char(ch as u8) {
       +                                                    ui.style().visuals.strong_text_color()
       +                                                } else {
       +                                                    ui.style().visuals.text_color()
       +                                                };
       +
       +                                                ui.colored_label(
       +                                                    color,
       +                                                    RichText::new(ch.to_string()).font(
       +                                                        FontId::new(12.0, FontFamily::Monospace),
       +                                                    ),
       +                                                );
       +                                            }
       +                                        });
       +
       +                                        ui.horizontal(|ui| {
       +                                            ui.spacing_mut().item_spacing =
       +                                                eframe::epaint::Vec2::new(0.0, 0.0);
       +                                            for ch in 'P'..='~' {
       +                                                let color = if font.has_char(ch as u8) {
       +                                                    ui.style().visuals.strong_text_color()
       +                                                } else {
       +                                                    ui.style().visuals.text_color()
       +                                                };
       +
       +                                                ui.colored_label(
       +                                                    color,
       +                                                    RichText::new(ch.to_string()).font(
       +                                                        FontId::new(12.0, FontFamily::Monospace),
       +                                                    ),
       +                                                );
       +                                            }
       +                                        });
       +                                    });
       +
       +                                    if let Some(img) = self.image_cache.get(&filtered_fonts[row]) {
       +                                        img.show_scaled(ui, 0.5);
       +                                    } else {
       +                                        let mut buffer = Buffer::new((100, 11));
       +                                        let mut pos = UPosition::default();
       +                                        let attr = TextAttribute::default();
       +                                        let outline_style = 0;
       +
       +                                        for ch in "HELLO".bytes() {
       +                                            let opt_size: Option<Size> = font.render(
       +                                                &mut buffer,
       +                                                0,
       +                                                pos,
       +                                                attr,
       +                                                outline_style,
       +                                                ch,
       +                                            );
       +                                            if let Some(size) = opt_size {
       +                                                pos.x += size.width + font.spaces as usize;
       +                                            }
       +                                        }
       +                                        let img = create_retained_image(&buffer);
       +                                        img.show_scaled(ui, 0.5);
       +                                        self.image_cache.insert(filtered_fonts[row], img);
       +                                    }
       +                                });
       +                            }
       +                        },
       +                    );
       +                }
       +            });
       +
       +            modal.buttons(ui, |ui| {
       +                if ui
       +                    .button(fl!(crate::LANGUAGE_LOADER, "select-font-dialog-select"))
       +                    .clicked()
       +                {
       +                    self.do_select = true;
       +                    result = true;
       +                }
       +                if ui
       +                    .button(fl!(crate::LANGUAGE_LOADER, "new-file-cancel"))
       +                    .clicked()
       +                {
       +                    result = true;
       +                }
       +            });
       +        });
       +        modal.open();
       +        result
       +    }
       +
       +    fn should_commit(&self) -> bool {
       +        self.do_select
       +    }
       +
       +    fn commit_self(&self, _window: &mut MainWindow) -> crate::TerminalResult<bool> {
       +        *self.selected_font_arc.lock().unwrap() = self.selected_font;
       +        Ok(true)
       +    }
       +}
       +
       +pub fn create_retained_image(buf: &Buffer) -> RetainedImage {
       +    let font_size = buf.get_font(0).unwrap().size;
       +
       +    let px_width = buf.get_width() * font_size.width;
       +    let px_height = buf.get_height() * font_size.height;
       +    let line_bytes = px_width * 3;
       +    let mut pixels = vec![0; line_bytes * px_height];
       +
       +    for y in 0..buf.get_height() {
       +        for x in 0..buf.get_width() {
       +            let ch = buf.get_char((x, y));
       +            let font = buf.get_font(ch.get_font_page()).unwrap();
       +
       +            let (fg_r, fg_g, fg_b) =
       +                buf.palette.colors[ch.attribute.get_foreground() as usize].get_rgb();
       +            let (bg_r, bg_g, bg_b) =
       +                buf.palette.colors[ch.attribute.get_background() as usize].get_rgb();
       +
       +            if let Some(glyph) = font.get_glyph(ch.ch) {
       +                for cy in 0..font_size.height {
       +                    for cx in 0..font_size.width {
       +                        let offset = (x * font_size.width + cx) * 3
       +                            + (y * font_size.height + cy) * line_bytes;
       +                        if glyph.data[cy] & (128 >> cx) != 0 {
       +                            pixels[offset] = fg_r;
       +                            pixels[offset + 1] = fg_g;
       +                            pixels[offset + 2] = fg_b;
       +                        } else {
       +                            pixels[offset] = bg_r;
       +                            pixels[offset + 1] = bg_g;
       +                            pixels[offset + 2] = bg_b;
       +                        }
       +                    }
       +                }
       +            }
       +        }
       +    }
       +    RetainedImage::from_color_image(
       +        "buf_img",
       +        ColorImage::from_rgb([px_width, px_height], &pixels),
       +    )
       +}
   DIR diff --git a/src/ui/dialogs/select_outline_dialog.rs b/src/ui/dialogs/select_outline_dialog.rs
       @@ -101,7 +101,7 @@ const OUTLINE_FONT_CHAR: [u8; 48] = [
        impl ModalDialog for SelectOutlineDialog {
            fn show(&mut self, ctx: &egui::Context) -> bool {
                let mut result = false;
       -        let modal = Modal::new(ctx, "my_modal");
       +        let modal = Modal::new(ctx, "select_outline_dialog");
        
                modal.show(|ui| {
                    modal.title(
   DIR diff --git a/src/ui/dialogs/set_canvas_size_dialog.rs b/src/ui/dialogs/set_canvas_size_dialog.rs
       @@ -24,7 +24,7 @@ impl SetCanvasSizeDialog {
        impl ModalDialog for SetCanvasSizeDialog {
            fn show(&mut self, ctx: &egui::Context) -> bool {
                let mut result = false;
       -        let modal = Modal::new(ctx, "my_modal");
       +        let modal = Modal::new(ctx, "set_canvas_size_dialog");
        
                modal.show(|ui| {
                    modal.title(ui, fl!(crate::LANGUAGE_LOADER, "edit-canvas-size-title"));
   DIR diff --git a/src/ui/document.rs b/src/ui/document.rs
       @@ -21,7 +21,6 @@ pub trait Document {
        
            fn get_ansi_editor_mut(&mut self) -> Option<&mut AnsiEditor>;
            fn get_ansi_editor(&self) -> Option<&AnsiEditor>;
       -
        }
        
        pub struct DocumentOptions {
   DIR diff --git a/src/ui/editor/mod.rs b/src/ui/editor/mod.rs
       @@ -323,7 +323,7 @@ impl AnsiEditor {
                self.buffer_view.lock().buf.layers[self.cur_layer].get_char(pos)
            }
        
       -    pub fn set_char(&mut self, pos: impl Into <UPosition>, dos_char: AttributedChar) {
       +    pub fn set_char(&mut self, pos: impl Into<UPosition>, dos_char: AttributedChar) {
                let pos = pos.into();
                self.redo_stack.clear();
                let old = self.buffer_view.lock().buf.layers[self.cur_layer].get_char(pos);
       @@ -383,16 +383,10 @@ impl AnsiEditor {
            }
        
            fn cur_selection(&self) -> Option<icy_engine::Selection> {
       -        self
       -        .buffer_view
       -        .lock()
       -        .get_selection().clone()
       +        self.buffer_view.lock().get_selection().clone()
            }
            fn clear_selection(&self) {
       -        self
       -        .buffer_view
       -        .lock()
       -        .clear_selection();
       +        self.buffer_view.lock().clear_selection();
            }
        
            pub fn type_key(&mut self, char_code: char) {
       @@ -417,7 +411,7 @@ impl AnsiEditor {
                    println!("delete selection! {} - {} ", min, max);
                    for y in min.y..=max.y {
                        for x in min.x..=max.x {
       -                    self.set_char( (x, y), AttributedChar::invisible());
       +                    self.set_char((x, y), AttributedChar::invisible());
                        }
                    }
                    self.end_atomic_undo();
       @@ -434,20 +428,10 @@ impl AnsiEditor {
                if let Some(selection) = &self.cur_selection() {
                    let min = selection.min();
                    let max = selection.max();
       -            (
       -                min.x,
       -                min.y,
       -                max.x,
       -                max.y,
       -            )
       +            (min.x, min.y, max.x, max.y)
                } else {
                    let size = self.buffer_view.lock().buf.get_buffer_size();
       -            (
       -                0,
       -                0,
       -                size.width as i32 - 1,
       -                size.height as i32 - 1,
       -            )
       +            (0, 0, size.width as i32 - 1, size.height as i32 - 1)
                }
            }
        
   DIR diff --git a/src/ui/main_window.rs b/src/ui/main_window.rs
       @@ -1,4 +1,11 @@
       -use std::{cell::RefCell, fs, path::Path, rc::Rc, sync::{Arc, Mutex}, time::Duration};
       +use std::{
       +    cell::RefCell,
       +    fs,
       +    path::Path,
       +    rc::Rc,
       +    sync::{Arc, Mutex},
       +    time::Duration,
       +};
        
        use crate::{
            add_child, model::Tool, AnsiEditor, DockingContainer, Document, DocumentOptions, FontEditor,
       @@ -14,8 +21,8 @@ use icy_engine::{BitFont, Buffer, Position};
        
        pub struct MainWindow {
            pub tree: egui_tiles::Tree<Tab>,
       -    toasts: egui_notify::Toasts,
       -    
       +    pub toasts: egui_notify::Toasts,
       +
            pub tab_viewer: TabBehavior,
            pub gl: Arc<Context>,
        
       @@ -36,8 +43,8 @@ impl MainWindow {
        
            pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
                let mut fnt = crate::model::font_imp::FontTool {
       -            selected_font: 0,
       -            fonts: Vec::new(),
       +            selected_font: Arc::new(Mutex::new(0)),
       +            fonts: Arc::new(Mutex::new(Vec::new())),
                    sizes: Vec::new(),
                };
                fnt.load_fonts();
       @@ -135,7 +142,7 @@ impl MainWindow {
                    toasts: egui_notify::Toasts::default(),
                    tree: DockingContainer::default(),
                    gl: cc.gl.clone().unwrap(),
       -                        dialog_open: false,
       +            dialog_open: false,
                    modal_dialog: None,
                    id: 0,
                    left_panel: true,
       @@ -176,9 +183,12 @@ impl MainWindow {
                    Err(err) => {
                        log::error!("Error loading file: {}", err);
                        self.toasts
       -                .error(fl!(crate::LANGUAGE_LOADER, "error-load-file", error = err.to_string()))
       -                .set_duration(Some(Duration::from_secs(5)));
       -
       +                    .error(fl!(
       +                        crate::LANGUAGE_LOADER,
       +                        "error-load-file",
       +                        error = err.to_string()
       +                    ))
       +                    .set_duration(Some(Duration::from_secs(5)));
                    }
                }
            }
       @@ -257,8 +267,6 @@ impl MainWindow {
                }
            }
        
       -
       -
            pub fn get_active_document_mut(&mut self) -> Option<&mut Box<dyn Document>> {
                if let Some(pane) = self.get_active_pane() {
                    return Some(&mut pane.doc);
       @@ -320,18 +328,21 @@ impl eframe::App for MainWindow {
                            }
                        }
                        crate::add_tool_switcher(ctx, ui, self);
       -                let modal = if let Some(tool) = self.tab_viewer.tools.clone().lock().unwrap().get_mut(self.tab_viewer.selected_tool) {
       +                if let Some(tool) = self
       +                    .tab_viewer
       +                    .tools
       +                    .clone()
       +                    .lock()
       +                    .unwrap()
       +                    .get_mut(self.tab_viewer.selected_tool)
       +                {
                            if let Some(doc) = self.get_active_document_mut() {
       -                       let doc = doc.get_ansi_editor();
       +                        let doc = doc.get_ansi_editor();
                                if let Some(editor) = doc {
                                    let tool_result = tool.show_ui(ctx, ui, editor);
       -                            tool_result.modal_dialog
       -                        } else { None }
       -                    } else { None }
       -                } else { None };
       -
       -                if modal.is_some() {
       -                    self.modal_dialog = modal;
       +                            self.handle_message(tool_result);
       +                        }
       +                    }
                        }
                    });
        
       @@ -391,7 +402,7 @@ impl eframe::App for MainWindow {
        
            fn on_exit(&mut self, _gl: Option<&glow::Context>) {
                /* TODO
       -        
       +
                self.enumerate_documents( move |doc| {
                    doc.destroy(gl);
                });*/
   DIR diff --git a/src/ui/messages.rs b/src/ui/messages.rs
       @@ -1,9 +1,17 @@
       -use std::path::PathBuf;
       +use std::{
       +    cell::RefCell,
       +    path::PathBuf,
       +    rc::Rc,
       +    sync::{Arc, Mutex},
       +};
        
        use eframe::egui;
       -use icy_engine::Selection;
       +use icy_engine::{Selection, TheDrawFont};
        
       -use crate::{MainWindow, NewFileDialog, OpenFileDialog, SaveFileDialog, SelectOutlineDialog};
       +use crate::{
       +    MainWindow, NewFileDialog, OpenFileDialog, SaveFileDialog, SelectCharacterDialog,
       +    SelectOutlineDialog,
       +};
        
        pub enum Message {
            NewFile,
       @@ -29,6 +37,9 @@ pub enum Message {
            DeleteSelection,
        
            ShowAboutDialog,
       +    ShowCharacterSelectionDialog(Rc<RefCell<char>>),
       +    SelectFontDialog(Arc<Mutex<Vec<TheDrawFont>>>, Arc<Mutex<i32>>),
       +    ShowError(String),
        }
        
        pub const CTRL_SHIFT: egui::Modifiers = egui::Modifiers {
       @@ -139,6 +150,19 @@ impl MainWindow {
                        }
                    }
        
       +            Message::ShowCharacterSelectionDialog(ch) => {
       +                if let Some(doc) = self.get_active_document_mut() {
       +                    let doc = doc.get_ansi_editor_mut();
       +                    if let Some(editor) = doc {
       +                        let buf = editor.buffer_view.clone();
       +                        self.open_dialog(SelectCharacterDialog::new(buf, ch));
       +                    }
       +                }
       +            }
       +            Message::SelectFontDialog(fonts, selected_font) => {
       +                self.open_dialog(crate::SelectFontDialog::new(fonts, selected_font));
       +            }
       +
                    Message::EditSauce => {
                        let mut buffer_opt = None;
                        if let Some(doc) = self.get_active_document_mut() {
       @@ -244,6 +268,11 @@ impl MainWindow {
                    Message::ShowAboutDialog => {
                        self.open_dialog(crate::AboutDialog::default());
                    }
       +
       +            Message::ShowError(msg) => {
       +                log::error!("{msg}");
       +                self.toasts.error(msg);
       +            }
                }
            }
        }
   DIR diff --git a/src/ui/top_bar.rs b/src/ui/top_bar.rs
       @@ -13,21 +13,15 @@ impl MainWindow {
                frame: &mut eframe::Frame,
            ) -> Option<Message> {
                let mut result = None;
       -        TopBottomPanel::top("top_panel")
       -            .show(ctx, |ui| {
       -                result = self.main_menu(ui, frame);
       -            });
       +        TopBottomPanel::top("top_panel").show(ctx, |ui| {
       +            result = self.main_menu(ui, frame);
       +        });
                result
            }
        
       -    fn main_menu(
       -        &mut self,
       -        ui: &mut Ui,
       -        frame: &mut eframe::Frame,
       -    ) -> Option<Message> {
       +    fn main_menu(&mut self, ui: &mut Ui, frame: &mut eframe::Frame) -> Option<Message> {
                let mut result = None;
                menu::bar(ui, |ui| {
       -
                    let mut buffer_opt = None;
                    if let Some(doc) = self.get_active_document_mut() {
                        buffer_opt = doc.get_ansi_editor_mut();
       @@ -362,7 +356,7 @@ impl MainWindow {
            fn top_bar_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
                    // From right-to-left:
       -/*
       +            /*
                    if hypex_ui::CUSTOM_WINDOW_DECORATIONS {
                        ui.add_space(8.0);
                        hypex_ui::native_window_buttons_ui(frame, ui);