URI: 
       Track API changes. - 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 b4880533c6d16ec3b3ef75ae56209e3439d298cd
   DIR parent 126a0c2d1b57582693b3ca2dadbd9d5a2664b3f1
  HTML Author: Mike Krüger <mkrueger@posteo.de>
       Date:   Tue,  5 Sep 2023 18:54:31 +0200
       
       Track API changes.
       
       Fixed dead lock on char selection.
       
       Diffstat:
         M i18n/en/icy_draw.ftl                |       5 +++++
         M src/model/tools/brush_imp.rs        |      81 ++++++++++++++++++++++++++++++-
         M src/model/tools/draw_rectangle_fil… |       1 +
         M src/model/tools/fill_imp.rs         |       2 +-
         M src/model/tools/font_imp.rs         |       2 +-
         M src/model/tools/line_imp.rs         |       2 +-
         M src/model/tools/mod.rs              |      15 +++++----------
         M src/model/tools/paste_tool.rs       |       1 +
         M src/ui/dialogs/edit_sauce_dialog.rs |       4 ++--
         M src/ui/dialogs/export_file_dialog/… |       2 +-
         M src/ui/dialogs/export_file_dialog/… |      62 ++++++++++++++++---------------
         M src/ui/dialogs/export_file_dialog/… |       2 +-
         M src/ui/dialogs/resize_layer_dialog… |       1 +
         M src/ui/dialogs/select_character_di… |     234 ++++++++++++++++---------------
         M src/ui/dialogs/select_font_dialog.… |       2 +-
         M src/ui/dialogs/set_canvas_size_dia… |       1 +
         M src/ui/document.rs                  |       1 +
         M src/ui/document_docking.rs          |       1 +
         M src/ui/editor/ansi/mod.rs           |      12 +++++++++---
         M src/ui/editor/bitfont/mod.rs        |       1 +
         M src/ui/editor/charfont/mod.rs       |       4 +++-
         M src/ui/main_window.rs               |      20 ++++++++++++++------
         M src/ui/messages.rs                  |      52 ++++++++++++++++++++++++-------
         M src/ui/top_bar.rs                   |      26 ++++++++++++++++++++++++++
       
       24 files changed, 349 insertions(+), 185 deletions(-)
       ---
   DIR diff --git a/i18n/en/icy_draw.ftl b/i18n/en/icy_draw.ftl
       @@ -20,6 +20,9 @@ menu-redo-op=Redo: { $op }
        menu-cut=Cut
        menu-copy=Copy
        menu-paste=Paste
       +menu-paste-as=Paste as
       +menu-paste-as-new-image=New image
       +menu-paste-as-brush=Brush
        menu-erase=Erase
        menu-flipx=Flip X
        menu-flipy=Flip Y
       @@ -46,6 +49,8 @@ tool-shade=Shade
        tool-colorize=Colorize
        tool-size-label=Size:
        tool-half-block=Half block
       +tool-custom-brush=Custom brush
       +
        toolbar-new=New
        
        new-file-title=New File
   DIR diff --git a/src/model/tools/brush_imp.rs b/src/model/tools/brush_imp.rs
       @@ -4,11 +4,11 @@ use eframe::{
        };
        use egui_extras::RetainedImage;
        use i18n_embed_fl::fl;
       -use icy_engine::{editor::AtomicUndoGuard, AttributedChar};
       +use icy_engine::{editor::AtomicUndoGuard, AttributedChar, Layer, Size, TextPane};
        use icy_engine_egui::TerminalCalc;
        use std::{cell::RefCell, rc::Rc};
        
       -use crate::{AnsiEditor, Event, Message};
       +use crate::{create_retained_image, AnsiEditor, Event, Message};
        
        use super::{Position, Tool};
        
       @@ -17,8 +17,11 @@ pub enum BrushType {
            Shade,
            Solid,
            Color,
       +    Custom,
        }
        
       +pub static mut CUSTOM_BRUSH: Option<Layer> = None;
       +
        pub struct BrushTool {
            pub use_fore: bool,
            pub use_back: bool,
       @@ -27,6 +30,8 @@ pub struct BrushTool {
        
            pub undo_op: Option<AtomicUndoGuard>,
        
       +    pub custom_brush: Option<Layer>,
       +    pub image: Option<RetainedImage>,
            pub brush_type: BrushType,
        }
        
       @@ -37,6 +42,10 @@ impl BrushTool {
                let center = pos + mid;
                let gradient = ['\u{00B0}', '\u{00B1}', '\u{00B2}', '\u{00DB}'];
                let caret_attr = editor.buffer_view.lock().get_caret().get_attribute();
       +        if matches!(self.brush_type, BrushType::Custom) {
       +            editor.join_overlay("brush");
       +            return;
       +        }
        
                for y in 0..self.size {
                    for x in 0..self.size {
       @@ -79,6 +88,7 @@ impl BrushTool {
                                    AttributedChar::new(ch.ch, attribute),
                                );
                            }
       +                    _ => {}
                        }
                    }
                }
       @@ -144,9 +154,76 @@ impl Tool for BrushTool {
                    BrushType::Color,
                    fl!(crate::LANGUAGE_LOADER, "tool-colorize"),
                );
       +
       +        unsafe {
       +            if CUSTOM_BRUSH.is_some() {
       +                self.custom_brush = CUSTOM_BRUSH.take();
       +
       +                let mut layer = self.custom_brush.as_ref().unwrap().clone();
       +                layer.set_offset((0, 0));
       +                layer.role = icy_engine::Role::Normal;
       +                let mut buf = icy_engine::Buffer::new(layer.get_size());
       +                layer.title = buf.layers[0].title.clone();
       +                buf.layers.clear();
       +                buf.layers.push(layer);
       +                self.image = Some(create_retained_image(&buf));
       +            }
       +        }
       +
       +        if self.custom_brush.is_some() {
       +            ui.radio_value(
       +                &mut self.brush_type,
       +                BrushType::Custom,
       +                fl!(crate::LANGUAGE_LOADER, "tool-custom-brush"),
       +            );
       +            if let Some(image) = &self.image {
       +                let w = ui.available_width() - 16.0;
       +                let scale = w / image.width() as f32;
       +                ui.image(
       +                    image.texture_id(ui.ctx()),
       +                    Vec2::new(image.width() as f32 * scale, image.height() as f32 * scale),
       +                );
       +            }
       +        }
                result
            }
        
       +    fn handle_hover(
       +        &mut self,
       +        _ui: &egui::Ui,
       +        response: egui::Response,
       +        editor: &mut AnsiEditor,
       +        cur: Position,
       +    ) -> egui::Response {
       +        if matches!(self.brush_type, BrushType::Custom) {
       +            editor.clear_overlay_layer();
       +            if let Some(layer) = editor
       +                .buffer_view
       +                .lock()
       +                .get_buffer_mut()
       +                .get_overlay_layer()
       +            {
       +                if let Some(brush) = &self.custom_brush {
       +                    let mid = Position::new(-(brush.get_width() / 2), -(brush.get_height() / 2));
       +                    for y in 0..brush.get_height() {
       +                        for x in 0..brush.get_width() {
       +                            let pos = Position::new(x, y);
       +                            let ch = brush.get_char(pos);
       +                            layer.set_char(
       +                                cur + pos + mid,
       +                                AttributedChar::new(ch.ch, ch.attribute),
       +                            );
       +                        }
       +                    }
       +                }
       +            }
       +        } else {
       +            editor.buffer_view.lock().get_buffer_mut().remove_overlay();
       +        }
       +
       +        response
       +    }
       +
            fn handle_click(
                &mut self,
                editor: &mut AnsiEditor,
   DIR diff --git a/src/model/tools/draw_rectangle_filled_imp.rs b/src/model/tools/draw_rectangle_filled_imp.rs
       @@ -104,6 +104,7 @@ impl Tool for DrawRectangleFilledTool {
                start: Position,
                cur: Position,
            ) -> egui::Response {
       +        editor.buffer_view.lock().get_buffer_mut().remove_overlay();
                editor.clear_overlay_layer();
        
                let mut lines = ScanLines::new(1);
   DIR diff --git a/src/model/tools/fill_imp.rs b/src/model/tools/fill_imp.rs
       @@ -2,7 +2,7 @@ use std::collections::HashSet;
        
        use eframe::egui;
        use i18n_embed_fl::fl;
       -use icy_engine::{AttributedChar, TextAttribute};
       +use icy_engine::{AttributedChar, TextAttribute, TextPane};
        
        use crate::{AnsiEditor, Message};
        
   DIR diff --git a/src/model/tools/font_imp.rs b/src/model/tools/font_imp.rs
       @@ -11,7 +11,7 @@ use eframe::{
            egui::{self, RichText},
            epaint::{FontFamily, FontId},
        };
       -use icy_engine::{Rectangle, Size, TextAttribute, TheDrawFont};
       +use icy_engine::{Rectangle, Size, TextAttribute, TextPane, TheDrawFont};
        use walkdir::{DirEntry, WalkDir};
        pub struct FontTool {
            pub selected_font: Arc<Mutex<i32>>,
   DIR diff --git a/src/model/tools/line_imp.rs b/src/model/tools/line_imp.rs
       @@ -1,6 +1,6 @@
        use eframe::egui;
        use i18n_embed_fl::fl;
       -use icy_engine::{AttributedChar, Rectangle, TextAttribute};
       +use icy_engine::{AttributedChar, Rectangle, TextAttribute, TextPane};
        use icy_engine_egui::TerminalCalc;
        
        use crate::{model::ScanLines, AnsiEditor, Message};
   DIR diff --git a/src/model/tools/mod.rs b/src/model/tools/mod.rs
       @@ -248,15 +248,12 @@ pub trait Tool {
                            editor.delete_selection();
                        } else {
                            let pos = editor.get_caret_position();
       -                    let end = editor.buffer_view.lock().get_buffer().get_width() - 1;
       +                    let end = editor.buffer_view.lock().get_width() - 1;
                            for i in pos.x..end {
                                let next = editor.get_char_from_cur_layer(Position::new(i + 1, pos.y));
                                editor.set_char(Position::new(i, pos.y), next);
                            }
       -                    let last_pos = Position::new(
       -                        editor.buffer_view.lock().get_buffer().get_width() - 1,
       -                        pos.y,
       -                    );
       +                    let last_pos = Position::new(editor.buffer_view.lock().get_width() - 1, pos.y);
                            editor.set_char(last_pos, AttributedChar::invisible());
                        }
                    }
       @@ -279,15 +276,13 @@ pub trait Tool {
                            } else {*/
                            editor.set_caret_position(pos + Position::new(-1, 0));
                            if editor.buffer_view.lock().get_caret().insert_mode {
       -                        let end = editor.buffer_view.lock().get_buffer().get_width() - 1;
       +                        let end = editor.buffer_view.lock().get_width() - 1;
                                for i in pos.x..end {
                                    let next = editor.get_char_from_cur_layer(Position::new(i + 1, pos.y));
                                    editor.set_char(Position::new(i, pos.y), next);
                                }
       -                        let last_pos = Position::new(
       -                            editor.buffer_view.lock().get_buffer().get_width() - 1,
       -                            pos.y,
       -                        );
       +                        let last_pos =
       +                            Position::new(editor.buffer_view.lock().get_width() - 1, pos.y);
                                editor.set_char(last_pos, AttributedChar::invisible());
                            } else {
                                let pos = editor.get_caret_position();
   DIR diff --git a/src/model/tools/paste_tool.rs b/src/model/tools/paste_tool.rs
       @@ -1,6 +1,7 @@
        use super::{Event, Position, Tool};
        use crate::{AnsiEditor, Message};
        use eframe::egui;
       +use icy_engine::TextPane;
        use icy_engine_egui::TerminalCalc;
        
        #[derive(Default)]
   DIR diff --git a/src/ui/dialogs/edit_sauce_dialog.rs b/src/ui/dialogs/edit_sauce_dialog.rs
       @@ -1,13 +1,13 @@
        use eframe::egui::{self, Layout};
        use egui_modal::Modal;
        use i18n_embed_fl::fl;
       -use icy_engine::{SauceString, SauceData};
       +use icy_engine::{SauceData, SauceString};
        
        use crate::{AnsiEditor, Message, ModalDialog, TerminalResult};
        
        pub struct EditSauceDialog {
            pub should_commit: bool,
       -    pub sauce_data: SauceData
       +    pub sauce_data: SauceData,
        }
        
        impl EditSauceDialog {
   DIR diff --git a/src/ui/dialogs/export_file_dialog/ascii.rs b/src/ui/dialogs/export_file_dialog/ascii.rs
       @@ -1,4 +1,4 @@
       -use eframe::egui::{self, Layout, Ui};
       +use eframe::egui::{self, Ui};
        use i18n_embed_fl::fl;
        use icy_engine::SaveOptions;
        
   DIR diff --git a/src/ui/dialogs/export_file_dialog/avatar.rs b/src/ui/dialogs/export_file_dialog/avatar.rs
       @@ -1,45 +1,47 @@
       -use eframe::egui::{self, Layout, Ui};
       +use eframe::egui::{self, Ui};
        use i18n_embed_fl::fl;
        use icy_engine::{SaveOptions, ScreenPreperation};
        
        pub fn create_settings_page(ui: &mut Ui, options: &mut SaveOptions) {
            ui.vertical(|ui| {
       -
                ui.horizontal(|ui| {
       -
                    ui.label(fl!(
                        crate::LANGUAGE_LOADER,
                        "export-video-preparation-label"
                    ));
        
       -        let label = match options.screen_preparation {
       -            ScreenPreperation::None => fl!(crate::LANGUAGE_LOADER, "export-video-preparation-None"),
       -            ScreenPreperation::ClearScreen => {
       -                fl!(crate::LANGUAGE_LOADER, "export-video-preparation-Clear")
       -            }
       -            ScreenPreperation::Home => fl!(crate::LANGUAGE_LOADER, "export-video-preparation-Home"),
       -        };
       +            let label = match options.screen_preparation {
       +                ScreenPreperation::None => {
       +                    fl!(crate::LANGUAGE_LOADER, "export-video-preparation-None")
       +                }
       +                ScreenPreperation::ClearScreen => {
       +                    fl!(crate::LANGUAGE_LOADER, "export-video-preparation-Clear")
       +                }
       +                ScreenPreperation::Home => {
       +                    fl!(crate::LANGUAGE_LOADER, "export-video-preparation-Home")
       +                }
       +            };
        
       -        egui::ComboBox::from_id_source("screen_prep_combo")
       -            .selected_text(label)
       -            .width(150.)
       -            .show_ui(ui, |ui| {
       -                ui.selectable_value(
       -                    &mut options.screen_preparation,
       -                    ScreenPreperation::None,
       -                    fl!(crate::LANGUAGE_LOADER, "export-video-preparation-None"),
       -                );
       -                ui.selectable_value(
       -                    &mut options.screen_preparation,
       -                    ScreenPreperation::ClearScreen,
       -                    fl!(crate::LANGUAGE_LOADER, "export-video-preparation-Clear"),
       -                );
       -                ui.selectable_value(
       -                    &mut options.screen_preparation,
       -                    ScreenPreperation::Home,
       -                    fl!(crate::LANGUAGE_LOADER, "export-video-preparation-Home"),
       -                );
       -            });
       +            egui::ComboBox::from_id_source("screen_prep_combo")
       +                .selected_text(label)
       +                .width(150.)
       +                .show_ui(ui, |ui| {
       +                    ui.selectable_value(
       +                        &mut options.screen_preparation,
       +                        ScreenPreperation::None,
       +                        fl!(crate::LANGUAGE_LOADER, "export-video-preparation-None"),
       +                    );
       +                    ui.selectable_value(
       +                        &mut options.screen_preparation,
       +                        ScreenPreperation::ClearScreen,
       +                        fl!(crate::LANGUAGE_LOADER, "export-video-preparation-Clear"),
       +                    );
       +                    ui.selectable_value(
       +                        &mut options.screen_preparation,
       +                        ScreenPreperation::Home,
       +                        fl!(crate::LANGUAGE_LOADER, "export-video-preparation-Home"),
       +                    );
       +                });
                });
                ui.add(egui::Checkbox::new(
                    &mut options.save_sauce,
   DIR diff --git a/src/ui/dialogs/export_file_dialog/mod.rs b/src/ui/dialogs/export_file_dialog/mod.rs
       @@ -2,7 +2,7 @@
        
        use std::path::PathBuf;
        
       -use eframe::egui::{self, Layout, TextEdit, Ui};
       +use eframe::egui::{self, TextEdit, Ui};
        use egui_file::FileDialog;
        use egui_modal::Modal;
        use i18n_embed_fl::fl;
   DIR diff --git a/src/ui/dialogs/resize_layer_dialog.rs b/src/ui/dialogs/resize_layer_dialog.rs
       @@ -1,6 +1,7 @@
        use eframe::egui::{self, Layout};
        use egui_modal::Modal;
        use i18n_embed_fl::fl;
       +use icy_engine::TextPane;
        
        use crate::{AnsiEditor, Message, ModalDialog, TerminalResult};
        
   DIR diff --git a/src/ui/dialogs/select_character_dialog.rs b/src/ui/dialogs/select_character_dialog.rs
       @@ -33,41 +33,40 @@ impl ModalDialog for SelectCharacterDialog {
            fn show(&mut self, ctx: &egui::Context) -> bool {
                let mut result = false;
                let modal = Modal::new(ctx, "select_character_dialog");
       -
                modal.show(|ui| {
                    modal.title(ui, fl!(crate::LANGUAGE_LOADER, "select-character-title"));
       -            let buffer_view = self.buf.lock();
       -            let font_page = buffer_view.get_caret().get_font_page();
       -            let font = buffer_view.get_buffer().get_font(font_page).unwrap();
       +            let font_page = self.buf.lock().get_caret().get_font_page();
                    let scale = 4.;
        
                    //   ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| {
                    modal.frame(ui, |ui| {
       -                let (_id, stroke_rect) = ui.allocate_space(Vec2::new(
       -                    scale * font.size.width as f32,
       -                    scale * font.size.height as f32,
       -                ));
       -                let painter = ui.painter_at(stroke_rect);
       -                painter.rect_filled(stroke_rect, Rounding::none(), Color32::BLACK);
       -                let s = font.size;
       -
       -                let col = Color32::GRAY;
       -                let ch = unsafe { char::from_u32_unchecked(self.selected_ch as u32) };
       -                if let Some(glyph) = font.get_glyph(ch) {
       -                    for y in 0..s.height {
       -                        for x in 0..s.width {
       -                            if glyph.data[y as usize] & (128 >> x) != 0 {
       -                                painter.rect_filled(
       -                                    Rect::from_min_size(
       -                                        egui::Pos2::new(
       -                                            stroke_rect.left() + x as f32 * scale,
       -                                            stroke_rect.top() + y as f32 * scale,
       +                if let Some(font) = self.buf.lock().get_buffer().get_font(font_page) {
       +                    let (_id, stroke_rect) = ui.allocate_space(Vec2::new(
       +                        scale * font.size.width as f32,
       +                        scale * font.size.height as f32,
       +                    ));
       +                    let painter = ui.painter_at(stroke_rect);
       +                    painter.rect_filled(stroke_rect, Rounding::none(), Color32::BLACK);
       +                    let s = font.size;
       +
       +                    let col = Color32::GRAY;
       +                    let ch = unsafe { char::from_u32_unchecked(self.selected_ch as u32) };
       +                    if let Some(glyph) = font.get_glyph(ch) {
       +                        for y in 0..s.height {
       +                            for x in 0..s.width {
       +                                if glyph.data[y as usize] & (128 >> x) != 0 {
       +                                    painter.rect_filled(
       +                                        Rect::from_min_size(
       +                                            egui::Pos2::new(
       +                                                stroke_rect.left() + x as f32 * scale,
       +                                                stroke_rect.top() + y as f32 * scale,
       +                                            ),
       +                                            Vec2::new(scale, scale),
                                                ),
       -                                        Vec2::new(scale, scale),
       -                                    ),
       -                                    Rounding::none(),
       -                                    col,
       -                                );
       +                                        Rounding::none(),
       +                                        col,
       +                                    );
       +                                }
                                    }
                                }
                            }
       @@ -85,100 +84,107 @@ impl ModalDialog for SelectCharacterDialog {
                    let scale = 2.;
        
                    modal.frame(ui, |ui| {
       -                let (_id, stroke_rect) = ui.allocate_space(Vec2::new(
       -                    scale * font.size.width as f32 * 256. / 8.,
       -                    scale * font.size.height as f32 * 8.,
       -                ));
       -
       -                let painter = ui.painter_at(stroke_rect);
       -                painter.rect_filled(stroke_rect, Rounding::none(), Color32::BLACK);
       -                let s = font.size;
       -
       -                let mut hovered_char = -1;
       -
       -                if let Some(hover_pos) = ui.input(|i| i.pointer.hover_pos()) {
       -                    if stroke_rect.contains(hover_pos) {
       -                        let char_x = ((hover_pos.x - stroke_rect.left())
       -                            / scale
       -                            / font.size.width as f32) as i32;
       -                        let char_y = ((hover_pos.y - stroke_rect.top())
       -                            / scale
       -                            / font.size.height as f32) as i32;
       -                        hovered_char = char_x + 32 * char_y;
       +                if let Some(font) = self.buf.lock().get_buffer().get_font(font_page) {
       +                    let (_id, stroke_rect) = ui.allocate_space(Vec2::new(
       +                        scale * font.size.width as f32 * 256. / 8.,
       +                        scale * font.size.height as f32 * 8.,
       +                    ));
       +
       +                    let painter = ui.painter_at(stroke_rect);
       +                    painter.rect_filled(stroke_rect, Rounding::none(), Color32::BLACK);
       +                    let s = font.size;
       +
       +                    let mut hovered_char = -1;
       +
       +                    if let Some(hover_pos) = ui.input(|i| i.pointer.hover_pos()) {
       +                        if stroke_rect.contains(hover_pos) {
       +                            let char_x = ((hover_pos.x - stroke_rect.left())
       +                                / scale
       +                                / font.size.width as f32)
       +                                as i32;
       +                            let char_y = ((hover_pos.y - stroke_rect.top())
       +                                / scale
       +                                / font.size.height as f32)
       +                                as i32;
       +                            hovered_char = char_x + 32 * char_y;
       +                        }
       +                    }
       +                    if hovered_char > 0
       +                        && ui.input(|i| i.pointer.button_pressed(egui::PointerButton::Primary))
       +                    {
       +                        self.selected_ch = unsafe { char::from_u32_unchecked(hovered_char as u32) };
                            }
       -                }
       -                if hovered_char > 0
       -                    && ui.input(|i| i.pointer.button_pressed(egui::PointerButton::Primary))
       -                {
       -                    self.selected_ch = unsafe { char::from_u32_unchecked(hovered_char as u32) };
       -                }
        
       -                for i in 0..font.length {
       -                    let is_selected = i == self.selected_ch as i32;
       -                    let col = if hovered_char >= 0 && i == hovered_char {
       -                        Color32::WHITE
       -                    } else if is_selected {
       -                        Color32::GRAY
       -                    } else {
       -                        Color32::DARK_GRAY
       -                    };
       -
       -                    let ch = unsafe { char::from_u32_unchecked(i as u32) };
       -                    let xs = ((i % 32) as f32) * scale * font.size.width as f32;
       -                    let ys = ((i / 32) as f32) * scale * font.size.height as f32;
       -                    if let Some(glyph) = font.get_glyph(ch) {
       -                        for y in 0..s.height {
       -                            for x in 0..s.width {
       -                                if glyph.data[y as usize] & (128 >> x) != 0 {
       -                                    painter.rect_filled(
       -                                        Rect::from_min_size(
       -                                            egui::Pos2::new(
       -                                                xs + stroke_rect.left() + x as f32 * scale,
       -                                                ys + stroke_rect.top() + y as f32 * scale,
       +                    for i in 0..font.length {
       +                        let is_selected = i == self.selected_ch as i32;
       +                        let col = if hovered_char >= 0 && i == hovered_char {
       +                            Color32::WHITE
       +                        } else if is_selected {
       +                            Color32::GRAY
       +                        } else {
       +                            Color32::DARK_GRAY
       +                        };
       +
       +                        let ch = unsafe { char::from_u32_unchecked(i as u32) };
       +                        let xs = ((i % 32) as f32) * scale * font.size.width as f32;
       +                        let ys = ((i / 32) as f32) * scale * font.size.height as f32;
       +                        if let Some(glyph) = font.get_glyph(ch) {
       +                            for y in 0..s.height {
       +                                for x in 0..s.width {
       +                                    if glyph.data[y as usize] & (128 >> x) != 0 {
       +                                        painter.rect_filled(
       +                                            Rect::from_min_size(
       +                                                egui::Pos2::new(
       +                                                    xs + stroke_rect.left() + x as f32 * scale,
       +                                                    ys + stroke_rect.top() + y as f32 * scale,
       +                                                ),
       +                                                Vec2::new(scale, scale),
                                                    ),
       -                                            Vec2::new(scale, scale),
       -                                        ),
       -                                        Rounding::none(),
       -                                        col,
       -                                    );
       +                                            Rounding::none(),
       +                                            col,
       +                                        );
       +                                    }
                                        }
                                    }
                                }
                            }
       -                }
       -
       -                let xs = ((self.selected_ch as i32 % 32) as f32) * scale * font.size.width as f32;
       -                let ys = ((self.selected_ch as i32 / 32) as f32) * scale * font.size.height as f32;
       -
       -                let selected_rect = Rect::from_min_size(
       -                    egui::Pos2::new(stroke_rect.left() + xs, stroke_rect.top() + ys),
       -                    Vec2::new(
       -                        scale * font.size.width as f32,
       -                        scale * font.size.height as f32,
       -                    ),
       -                );
        
       -                painter.rect(
       -                    selected_rect,
       -                    Rounding::none(),
       -                    Color32::TRANSPARENT,
       -                    (2.0, Color32::LIGHT_BLUE),
       -                );
       -
       -                ui.horizontal(|ui| {
       -                    if hovered_char >= 0 {
       -                        ui.label(
       -                            RichText::new(fl!(crate::LANGUAGE_LOADER, "glyph-char-label")).small(),
       -                        );
       -                        ui.label(
       -                            RichText::new(format!("{0}/0x{0:02X}", hovered_char))
       -                                .small()
       -                                .color(Color32::WHITE),
       -                        );
       -                    } else {
       -                        ui.label("");
       -                    }
       -                });
       +                    let xs =
       +                        ((self.selected_ch as i32 % 32) as f32) * scale * font.size.width as f32;
       +                    let ys =
       +                        ((self.selected_ch as i32 / 32) as f32) * scale * font.size.height as f32;
       +
       +                    let selected_rect = Rect::from_min_size(
       +                        egui::Pos2::new(stroke_rect.left() + xs, stroke_rect.top() + ys),
       +                        Vec2::new(
       +                            scale * font.size.width as f32,
       +                            scale * font.size.height as f32,
       +                        ),
       +                    );
       +
       +                    painter.rect(
       +                        selected_rect,
       +                        Rounding::none(),
       +                        Color32::TRANSPARENT,
       +                        (2.0, Color32::LIGHT_BLUE),
       +                    );
       +
       +                    ui.horizontal(|ui| {
       +                        if hovered_char >= 0 {
       +                            ui.label(
       +                                RichText::new(fl!(crate::LANGUAGE_LOADER, "glyph-char-label"))
       +                                    .small(),
       +                            );
       +                            ui.label(
       +                                RichText::new(format!("{0}/0x{0:02X}", hovered_char))
       +                                    .small()
       +                                    .color(Color32::WHITE),
       +                            );
       +                        } else {
       +                            ui.label("");
       +                        }
       +                    });
       +                }
                    });
        
                    modal.buttons(ui, |ui| {
   DIR diff --git a/src/ui/dialogs/select_font_dialog.rs b/src/ui/dialogs/select_font_dialog.rs
       @@ -7,7 +7,7 @@ use eframe::{
        use egui_extras::RetainedImage;
        use egui_modal::Modal;
        use i18n_embed_fl::fl;
       -use icy_engine::{editor::EditState, Buffer, Rectangle, Size, TheDrawFont};
       +use icy_engine::{editor::EditState, Buffer, Rectangle, Size, TextPane, TheDrawFont};
        
        use crate::{MainWindow, Message};
        
   DIR diff --git a/src/ui/dialogs/set_canvas_size_dialog.rs b/src/ui/dialogs/set_canvas_size_dialog.rs
       @@ -1,6 +1,7 @@
        use eframe::egui::{self, Layout};
        use egui_modal::Modal;
        use i18n_embed_fl::fl;
       +use icy_engine::TextPane;
        
        use crate::{AnsiEditor, Message, ModalDialog, TerminalResult};
        
   DIR diff --git a/src/ui/document.rs b/src/ui/document.rs
       @@ -36,6 +36,7 @@ pub trait Document: UndoState + ClipboardHandler {
                &mut self,
                ui: &mut egui::Ui,
                cur_tool: &mut Box<dyn Tool>,
       +        selected_tool: usize,
                options: &DocumentOptions,
            ) -> Option<Message>;
        
   DIR diff --git a/src/ui/document_docking.rs b/src/ui/document_docking.rs
       @@ -37,6 +37,7 @@ impl egui_tiles::Behavior<DocumentTab> for DocumentBehavior {
                self.message = pane.doc.lock().unwrap().show_ui(
                    ui,
                    &mut self.tools.lock().unwrap()[self.selected_tool],
       +            self.selected_tool,
                    &self.document_options,
                );
                egui_tiles::UiResponse::None
   DIR diff --git a/src/ui/editor/ansi/mod.rs b/src/ui/editor/ansi/mod.rs
       @@ -16,6 +16,7 @@ use icy_engine::{
            editor::{AtomicUndoGuard, UndoState},
            util::{pop_data, pop_sixel_image, push_data, BUFFER_DATA},
            AttributedChar, Buffer, EngineResult, Line, Position, Rectangle, SaveOptions, TextAttribute,
       +    TextPane,
        };
        
        use icy_engine_egui::{
       @@ -24,7 +25,7 @@ use icy_engine_egui::{
        
        use crate::{
            model::{MKey, MModifiers, Tool},
       -    ClipboardHandler, Document, DocumentOptions, Message, TerminalResult,
       +    ClipboardHandler, Document, DocumentOptions, Message, TerminalResult, FIRST_TOOL,
        };
        
        pub enum Event {
       @@ -156,6 +157,7 @@ impl Document for AnsiEditor {
                &mut self,
                ui: &mut egui::Ui,
                cur_tool: &mut Box<dyn Tool>,
       +        selected_tool: usize,
                options: &DocumentOptions,
            ) -> Option<Message> {
                let mut message = None;
       @@ -164,6 +166,10 @@ impl Document for AnsiEditor {
                if self.buffer_view.lock().get_buffer().use_aspect_ratio() {
                    scale.y *= 1.35;
                }
       +        if selected_tool != FIRST_TOOL {
       +            self.buffer_view.lock().clear_selection();
       +        }
       +
                ui.allocate_ui(
                    Vec2::new(ui.available_width(), ui.available_height() - 35.0),
                    |ui| {
       @@ -267,7 +273,7 @@ impl AnsiEditor {
            pub fn set_caret_position(&mut self, pos: Position) {
                let buffer_view = &mut self.buffer_view.lock();
                let pos = Position::new(
       -            min(buffer_view.get_buffer().get_width() - 1, max(0, pos.x)),
       +            min(buffer_view.get_width() - 1, max(0, pos.x)),
                    min(buffer_view.get_buffer().get_line_count() - 1, max(0, pos.y)),
                );
                buffer_view.get_caret_mut().set_position(pos);
       @@ -345,7 +351,7 @@ impl AnsiEditor {
                //(self.outline_changed)(self);
            }
        
       -    pub fn save_content(&self, file_name: &Path, options: &SaveOptions) -> io::Result<bool> {
       +    pub fn save_content(&self, file_name: &Path, options: &SaveOptions) -> EngineResult<bool> {
                let mut f = File::create(file_name)?;
        
                let content = if let Some(ext) = file_name.extension() {
   DIR diff --git a/src/ui/editor/bitfont/mod.rs b/src/ui/editor/bitfont/mod.rs
       @@ -478,6 +478,7 @@ impl Document for BitFontEditor {
                &mut self,
                ui: &mut eframe::egui::Ui,
                _cur_tool: &mut Box<dyn Tool>,
       +        _selected_tool: usize,
                _options: &DocumentOptions,
            ) -> Option<Message> {
                let mut message = None;
   DIR diff --git a/src/ui/editor/charfont/mod.rs b/src/ui/editor/charfont/mod.rs
       @@ -72,6 +72,7 @@ impl Document for CharFontEditor {
                &mut self,
                ui: &mut egui::Ui,
                cur_tool: &mut Box<dyn Tool>,
       +        selected_tool: usize,
                options: &DocumentOptions,
            ) -> Option<Message> {
                egui::ComboBox::from_id_source("combobox1")
       @@ -95,7 +96,8 @@ impl Document for CharFontEditor {
                    });
        
                self.show_char_selector(ui);
       -        self.ansi_editor.show_ui(ui, cur_tool, options);
       +        self.ansi_editor
       +            .show_ui(ui, cur_tool, selected_tool, options);
                None
            }
        
   DIR diff --git a/src/ui/main_window.rs b/src/ui/main_window.rs
       @@ -18,7 +18,7 @@ use eframe::{
        };
        use glow::Context;
        use i18n_embed_fl::fl;
       -use icy_engine::{BitFont, Buffer, EngineResult, Position, TheDrawFont};
       +use icy_engine::{BitFont, Buffer, EngineResult, Position, TextPane, TheDrawFont};
        
        pub struct MainWindow {
            pub document_tree: egui_tiles::Tree<DocumentTab>,
       @@ -41,6 +41,7 @@ pub struct MainWindow {
        
        pub const PASTE_TOOL: usize = 0;
        pub const FIRST_TOOL: usize = 1;
       +pub const BRUSH_TOOL: usize = 3;
        
        impl MainWindow {
            pub fn create_id(&mut self) -> usize {
       @@ -72,6 +73,8 @@ impl MainWindow {
                        use_back: true,
                        use_fore: true,
                        undo_op: None,
       +                custom_brush: None,
       +                image: None,
                        brush_type: crate::model::brush_imp::BrushType::Shade,
                        char_code: Rc::new(RefCell::new('\u{00B0}')),
                    }),
       @@ -245,7 +248,7 @@ impl MainWindow {
                    Ok(mut buf) => {
                        let id = self.create_id();
                        buf.is_terminal_buffer = false;
       -                buf.set_buffer_height(buf.get_line_count());
       +                buf.set_height(buf.get_line_count());
                        let editor = AnsiEditor::new(&self.gl, id, buf);
                        add_child(&mut self.document_tree, Some(full_path), Box::new(editor));
                    }
       @@ -467,12 +470,17 @@ impl eframe::App for MainWindow {
                            ui.horizontal(|ui| {
                                ui.add_space(4.0);
                                ui.vertical(|ui| {
       -                            if let Some(doc) = self.get_active_document() {
       +                            let tool_result = if let Some(doc) = self.get_active_document() {
                                        if let Some(editor) = doc.lock().unwrap().get_ansi_editor() {
       -                                    let tool_result = tool.show_ui(ctx, ui, editor);
       -                                    self.handle_message(tool_result);
       +                                    tool.show_ui(ctx, ui, editor)
       +                                } else {
       +                                    None
                                        }
       -                            }
       +                            } else {
       +                                None
       +                            };
       +                            // can't handle message inside the lock
       +                            self.handle_message(tool_result);
                                });
                            });
                        }
   DIR diff --git a/src/ui/messages.rs b/src/ui/messages.rs
       @@ -1,4 +1,5 @@
        use std::{
       +    backtrace::Backtrace,
            cell::RefCell,
            path::PathBuf,
            rc::Rc,
       @@ -6,10 +7,13 @@ use std::{
        };
        
        use eframe::egui;
       -use icy_engine::{BitFont, EngineResult, Selection, Size, TheDrawFont};
       +use glow::Buffer;
       +use icy_engine::{
       +    util::pop_data, BitFont, EngineResult, Layer, Selection, Size, TextPane, TheDrawFont,
       +};
        
        use crate::{
       -    MainWindow, NewFileDialog, OpenFileDialog, SaveFileDialog, SelectCharacterDialog,
       +    AnsiEditor, MainWindow, NewFileDialog, OpenFileDialog, SaveFileDialog, SelectCharacterDialog,
            SelectOutlineDialog,
        };
        
       @@ -57,6 +61,8 @@ pub enum Message {
            Crop,
            Paste,
            ResizeBuffer(i32, i32),
       +    PasteAsNewImage,
       +    PasteAsBrush,
        }
        
        pub const CTRL_SHIFT: egui::Modifiers = egui::Modifiers {
       @@ -175,16 +181,11 @@ impl MainWindow {
                        }
                    }
                    Message::ShowCharacterSelectionDialog(ch) => {
       -                if let Some(editor) = self
       -                    .get_active_document()
       -                    .unwrap()
       -                    .lock()
       -                    .unwrap()
       -                    .get_ansi_editor()
       -                {
       +                self.run_editor_command(ch, |window, editor: &mut AnsiEditor, ch| {
                            let buf = editor.buffer_view.clone();
       -                    self.open_dialog(SelectCharacterDialog::new(buf, ch));
       -                }
       +                    window.open_dialog(SelectCharacterDialog::new(buf, ch));
       +                    None
       +                });
                    }
                    Message::SelectFontDialog(fonts, selected_font) => {
                        self.open_dialog(crate::SelectFontDialog::new(fonts, selected_font));
       @@ -441,6 +442,35 @@ impl MainWindow {
                            to_message(lock.get_edit_state_mut().resize_buffer(Size::new(w, h)))
                        });
                    }
       +
       +            Message::PasteAsNewImage => {
       +                if let Some(data) = pop_data(icy_engine::util::BUFFER_DATA) {
       +                    if let Some(mut layer) = Layer::from_clipboard_data(&data) {
       +                        layer.set_offset((0, 0));
       +                        layer.role = icy_engine::Role::Normal;
       +                        let mut buf = icy_engine::Buffer::new(layer.get_size());
       +                        layer.title = buf.layers[0].title.clone();
       +                        buf.layers.clear();
       +                        buf.layers.push(layer);
       +                        let id = self.create_id();
       +                        buf.is_terminal_buffer = false;
       +                        buf.set_height(buf.get_line_count());
       +                        let editor = AnsiEditor::new(&self.gl, id, buf);
       +                        crate::add_child(&mut self.document_tree, None, Box::new(editor));
       +                    }
       +                }
       +            }
       +
       +            Message::PasteAsBrush => {
       +                if let Some(data) = pop_data(icy_engine::util::BUFFER_DATA) {
       +                    if let Some(mut layer) = Layer::from_clipboard_data(&data) {
       +                        unsafe {
       +                            crate::model::brush_imp::CUSTOM_BRUSH = Some(layer);
       +                            self.document_behavior.selected_tool = crate::BRUSH_TOOL;
       +                        }
       +                    }
       +                }
       +            }
                }
            }
        }
   DIR diff --git a/src/ui/top_bar.rs b/src/ui/top_bar.rs
       @@ -4,6 +4,7 @@ use eframe::{
        };
        use egui_extras::RetainedImage;
        use i18n_embed_fl::fl;
       +use icy_engine::util::{pop_data, BUFFER_DATA};
        
        use crate::{button_with_shortcut, MainWindow, Message};
        
       @@ -212,6 +213,31 @@ impl MainWindow {
                                ui.close_menu();
                            }
                        }
       +
       +                ui.menu_button(fl!(crate::LANGUAGE_LOADER, "menu-paste-as"), |ui| {
       +                    let button = button_with_shortcut(
       +                        ui,
       +                        pop_data(BUFFER_DATA).is_some(),
       +                        fl!(crate::LANGUAGE_LOADER, "menu-paste-as-new-image"),
       +                        "",
       +                    );
       +                    if button.clicked() {
       +                        result = Some(Message::PasteAsNewImage);
       +                        ui.close_menu();
       +                    }
       +
       +                    let button = button_with_shortcut(
       +                        ui,
       +                        pop_data(BUFFER_DATA).is_some(),
       +                        fl!(crate::LANGUAGE_LOADER, "menu-paste-as-brush"),
       +                        "",
       +                    );
       +                    if button.clicked() {
       +                        result = Some(Message::PasteAsBrush);
       +                        ui.close_menu();
       +                    }
       +                });
       +
                        ui.separator();
                        if ui
                            .add_enabled(