URI: 
       Implemented outline font editor. - 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 b72963c17b064854d9acd6e24efcd5c9327b3b07
   DIR parent 4690574b9eb9ebab87fbba6f47a472bb98d58749
  HTML Author: Mike Krüger <mkrueger@posteo.de>
       Date:   Mon, 18 Sep 2023 17:15:24 +0200
       
       Implemented outline font editor.
       
       Improved tdf editor a bit in general & fixed some outline font rendering issues.
       
       Diffstat:
         M i18n/de/icy_draw.ftl                |      16 ++++++++++++++--
         M i18n/en/icy_draw.ftl                |      15 +++++++++++++--
         M src/model/tools/click_imp.rs        |      85 ++++++++++++++++++++++++-------
         M src/ui/dialogs/select_outline_dial… |      34 +++++++++++++++++++------------
         M src/ui/editor/ansi/mod.rs           |       3 +++
         M src/ui/editor/charfont/mod.rs       |     366 ++++++++++++++++++++++++++-----
       
       6 files changed, 428 insertions(+), 91 deletions(-)
       ---
   DIR diff --git a/i18n/de/icy_draw.ftl b/i18n/de/icy_draw.ftl
       @@ -348,4 +348,16 @@ palette_selector-c64_default_palette=C64 Farben
        palette_selector-ega_default_palette=EGA 64 Farben
        palette_selector-xterm_default_palette=XTerm erweiterte Palette
        palette_selector-viewdata_default_palette=Viewdata
       -palette_selector-extracted_from_buffer_default_label=Erzeugt aus offener Datei
       -\ No newline at end of file
       +palette_selector-extracted_from_buffer_default_label=Erzeugt aus offener Datei
       +
       +tdf-editor-outline_preview_label=Outline Vorschau
       +tdf-editor-draw_bg_checkbox=Zeichne Hintergrund
       +tdf-editor-clone_button=Klonen
       +tdf-editor-font_name_label=Font-Name:
       +tdf-editor-spacing_label=Zeichenabstand:
       +tdf-editor-no_font_selected_label=Kein Font ausgewählt
       +tdf-editor-font_type_label=Font-Typ:
       +tdf-editor-font_type_color=Farbe
       +tdf-editor-font_type_block=Block
       +tdf-editor-font_type_outline=Outline
       +tdf-editor-clear_char_button=Zeichen Löschen
       +\ No newline at end of file
   DIR diff --git a/i18n/en/icy_draw.ftl b/i18n/en/icy_draw.ftl
       @@ -351,4 +351,16 @@ palette_selector-c64_default_palette=C64 colors
        palette_selector-ega_default_palette=EGA 64 colors
        palette_selector-xterm_default_palette=XTerm extended colors
        palette_selector-viewdata_default_palette=Viewdata
       -palette_selector-extracted_from_buffer_default_label=Extracted from buffer
       -\ No newline at end of file
       +palette_selector-extracted_from_buffer_default_label=Extracted from buffer
       +
       +tdf-editor-outline_preview_label=Outline glyph preview
       +tdf-editor-draw_bg_checkbox=Use background
       +tdf-editor-clone_button=Clone
       +tdf-editor-font_name_label=Font Name:
       +tdf-editor-spacing_label=Spacing:
       +tdf-editor-no_font_selected_label=No font selected
       +tdf-editor-font_type_label=Font Type:
       +tdf-editor-font_type_color=Color
       +tdf-editor-font_type_block=Block
       +tdf-editor-font_type_outline=Outline
       +tdf-editor-clear_char_button=Clear Char
   DIR diff --git a/src/model/tools/click_imp.rs b/src/model/tools/click_imp.rs
       @@ -33,6 +33,7 @@ pub struct ClickTool {
            selection_drag: SelectionDrag,
            undo_op: Option<AtomicUndoGuard>,
        }
       +pub const VALID_OUTLINE_CHARS: &str = "ABCDEFGHIJKLMNO@&\u{F7} ";
        
        impl Tool for ClickTool {
            fn get_icon_name(&self) -> &'static RetainedImage {
       @@ -377,47 +378,93 @@ impl Tool for ClickTool {
        
                    MKey::Character(ch) => {
                        editor.buffer_view.lock().clear_selection();
       -                /*        if let MModifiers::Alt = modifier {
       -                    match key_code {
       -                        MKeyCode::KeyI => editor.insert_line(pos.y),
       -                        MKeyCode::KeyU => editor.pickup_color(pos),
       -                        MKeyCode::KeyY => editor.delete_line(pos.y),
       -                        MKeyCode::Unknown => {}
       +                let typed_char = unsafe { char::from_u32_unchecked(ch as u32) };
       +                if editor.outline_font_mode {
       +                    let typed_char = typed_char.to_ascii_uppercase();
       +                    if VALID_OUTLINE_CHARS.contains(typed_char) {
       +                        editor.type_key(typed_char);
       +                    } else if let '1'..='8' = typed_char {
       +                        editor.type_key(
       +                            VALID_OUTLINE_CHARS
       +                                .chars()
       +                                .nth(10 + typed_char as usize - b'1' as usize)
       +                                .unwrap(),
       +                        );
                            }
       -                    return Event::None;
       -                }*/
       -                editor.type_key(unsafe { char::from_u32_unchecked(ch as u32) });
       +                } else {
       +                    editor.type_key(typed_char);
       +                }
                    }
        
                    MKey::F1 => {
       -                handle_outline_insertion(editor, modifier, 0);
       +                if editor.outline_font_mode {
       +                    editor.type_key(VALID_OUTLINE_CHARS.chars().nth(0).unwrap());
       +                } else {
       +                    handle_outline_insertion(editor, modifier, 0);
       +                }
                    }
                    MKey::F2 => {
       -                handle_outline_insertion(editor, modifier, 1);
       +                if editor.outline_font_mode {
       +                    editor.type_key(VALID_OUTLINE_CHARS.chars().nth(1).unwrap());
       +                } else {
       +                    handle_outline_insertion(editor, modifier, 1);
       +                }
                    }
                    MKey::F3 => {
       -                handle_outline_insertion(editor, modifier, 2);
       +                if editor.outline_font_mode {
       +                    editor.type_key(VALID_OUTLINE_CHARS.chars().nth(2).unwrap());
       +                } else {
       +                    handle_outline_insertion(editor, modifier, 2);
       +                }
                    }
                    MKey::F4 => {
       -                handle_outline_insertion(editor, modifier, 3);
       +                if editor.outline_font_mode {
       +                    editor.type_key(VALID_OUTLINE_CHARS.chars().nth(3).unwrap());
       +                } else {
       +                    handle_outline_insertion(editor, modifier, 3);
       +                }
                    }
                    MKey::F5 => {
       -                handle_outline_insertion(editor, modifier, 4);
       +                if editor.outline_font_mode {
       +                    editor.type_key(VALID_OUTLINE_CHARS.chars().nth(4).unwrap());
       +                } else {
       +                    handle_outline_insertion(editor, modifier, 4);
       +                }
                    }
                    MKey::F6 => {
       -                handle_outline_insertion(editor, modifier, 5);
       +                if editor.outline_font_mode {
       +                    editor.type_key(VALID_OUTLINE_CHARS.chars().nth(5).unwrap());
       +                } else {
       +                    handle_outline_insertion(editor, modifier, 5);
       +                }
                    }
                    MKey::F7 => {
       -                handle_outline_insertion(editor, modifier, 6);
       +                if editor.outline_font_mode {
       +                    editor.type_key(VALID_OUTLINE_CHARS.chars().nth(6).unwrap());
       +                } else {
       +                    handle_outline_insertion(editor, modifier, 6);
       +                }
                    }
                    MKey::F8 => {
       -                handle_outline_insertion(editor, modifier, 7);
       +                if editor.outline_font_mode {
       +                    editor.type_key(VALID_OUTLINE_CHARS.chars().nth(7).unwrap());
       +                } else {
       +                    handle_outline_insertion(editor, modifier, 7);
       +                }
                    }
                    MKey::F9 => {
       -                handle_outline_insertion(editor, modifier, 8);
       +                if editor.outline_font_mode {
       +                    editor.type_key(VALID_OUTLINE_CHARS.chars().nth(8).unwrap());
       +                } else {
       +                    handle_outline_insertion(editor, modifier, 8);
       +                }
                    }
                    MKey::F10 => {
       -                handle_outline_insertion(editor, modifier, 9);
       +                if editor.outline_font_mode {
       +                    editor.type_key(VALID_OUTLINE_CHARS.chars().nth(9).unwrap());
       +                } else {
       +                    handle_outline_insertion(editor, modifier, 9);
       +                }
                    }
                    _ => {}
                }
   DIR diff --git a/src/ui/dialogs/select_outline_dialog.rs b/src/ui/dialogs/select_outline_dialog.rs
       @@ -25,6 +25,10 @@ impl Default for SelectOutlineDialog {
        }
        
        impl SelectOutlineDialog {
       +    pub fn get_outline_style(&self) -> usize {
       +        self.selected_outline
       +    }
       +
            fn draw_outline_glyph(&mut self, ui: &mut egui::Ui, outline_style: usize) {
                let scale = 1.;
                let border = 4.0;
       @@ -89,6 +93,22 @@ impl SelectOutlineDialog {
                    }
                }
            }
       +
       +    pub fn show_outline_ui(&mut self, ui: &mut egui::Ui, item_per_row: usize, spacing: Vec2) {
       +        for style in 0..TheDrawFont::OUTLINE_STYLES / item_per_row {
       +            ui.horizontal(|ui| {
       +                ui.add_space(4.0);
       +                for i in 0..item_per_row {
       +                    self.draw_outline_glyph(ui, style * item_per_row + i);
       +                    if i < item_per_row - 1 {
       +                        ui.add_space(spacing.x);
       +                    }
       +                }
       +            });
       +            ui.end_row();
       +            ui.add_space(spacing.y);
       +        }
       +    }
        }
        
        const OUTLINE_WIDTH: usize = 8;
       @@ -111,19 +131,7 @@ impl ModalDialog for SelectOutlineDialog {
        
                    modal.frame(ui, |ui| {
                        ui.add_space(8.0);
       -                for style in 0..TheDrawFont::OUTLINE_STYLES / 4 {
       -                    ui.horizontal(|ui| {
       -                        ui.add_space(4.0);
       -                        for i in 0..4 {
       -                            self.draw_outline_glyph(ui, style * 4 + i);
       -                            if i < 3 {
       -                                ui.add_space(8.0);
       -                            }
       -                        }
       -                    });
       -                    ui.end_row();
       -                    ui.add_space(8.0);
       -                }
       +                self.show_outline_ui(ui, 4, Vec2::new(8.0, 8.0));
                    });
        
                    modal.buttons(ui, |ui| {
   DIR diff --git a/src/ui/editor/ansi/mod.rs b/src/ui/editor/ansi/mod.rs
       @@ -41,6 +41,8 @@ pub struct AnsiEditor {
            pub buffer_view: Arc<eframe::epaint::mutex::Mutex<BufferView>>,
            pub is_inactive: bool,
        
       +    pub outline_font_mode: bool,
       +
            pub reference_image: Option<PathBuf>,
            // pub outline_changed: std::boxed::Box<dyn Fn(&Editor)>,
            //pub request_refresh: Box<dyn Fn ()>,
       @@ -238,6 +240,7 @@ impl AnsiEditor {
                    egui_id: Id::new(id),
                    guide: None,
                    raster: None,
       +            outline_font_mode: false,
                }
            }
        
   DIR diff --git a/src/ui/editor/charfont/mod.rs b/src/ui/editor/charfont/mod.rs
       @@ -1,21 +1,39 @@
        use std::{path::Path, sync::Arc};
        
       -use eframe::egui::{self, RichText, SidePanel, TextEdit, TopBottomPanel};
       -use icy_engine::{BitFont, Buffer, EngineResult, FontGlyph, Layer, Size, TextPane, TheDrawFont};
       +use eframe::{
       +    egui::{self, Button, RichText, ScrollArea, SidePanel, TextEdit, TopBottomPanel},
       +    epaint::{mutex::Mutex, Vec2},
       +};
       +use egui_extras::RetainedImage;
       +use i18n_embed_fl::fl;
       +use icy_engine::{
       +    AttributedChar, BitFont, Buffer, EngineResult, FontGlyph, Layer, Size, TextAttribute, TextPane,
       +    TheDrawFont,
       +};
       +use icy_engine_egui::{show_terminal_area, BufferView, MonitorSettings};
        
        use crate::{
       -    model::Tool, AnsiEditor, BitFontEditor, ClipboardHandler, Document, DocumentOptions,
       -    DrawGlyphStyle, Message, TerminalResult, UndoHandler,
       +    model::{click_imp::VALID_OUTLINE_CHARS, Tool},
       +    AnsiEditor, BitFontEditor, ClipboardHandler, Document, DocumentOptions, DrawGlyphStyle,
       +    Message, SelectOutlineDialog, TerminalResult, UndoHandler,
        };
        
        pub struct CharFontEditor {
       +    id: usize,
            font: BitFont,
            selected_char_opt: Option<char>,
            old_selected_char_opt: Option<char>,
       +
       +    outline_previewbuffer_view: Arc<Mutex<BufferView>>,
       +
            ansi_editor: AnsiEditor,
            selected_font: usize,
            fonts: Vec<TheDrawFont>,
            undostack_len: usize,
       +    last_update_preview: usize,
       +    outline_selection: crate::SelectOutlineDialog,
       +    draw_outline_bg: bool,
       +    opt_cheat_sheet: Option<RetainedImage>,
        }
        
        impl ClipboardHandler for CharFontEditor {
       @@ -96,35 +114,30 @@ impl Document for CharFontEditor {
                SidePanel::left("side_panel")
                    .default_width(200.0)
                    .show_inside(ui, |ui| {
       +                ui.add_space(4.0);
       +
                        if self.selected_font < self.fonts.len() {
       -                    egui::ComboBox::from_id_source("combobox1")
       -                        .selected_text(RichText::new(
       -                            self.fonts[self.selected_font].name.to_string(),
       -                        ))
       -                        .show_ui(ui, |ui| {
       -                            let mut changed = false;
       -                            let mut select_font = 0;
       -                            for (i, font) in self.fonts.iter().enumerate() {
       -                                if ui
       -                                    .selectable_value(&mut self.selected_font, i, &font.name)
       -                                    .clicked()
       -                                {
       -                                    select_font = i;
       -                                    changed = true;
       -                                }
       -                            }
       -                            if changed {
       +                    ScrollArea::vertical().show(ui, |ui| {
       +                        ui.style_mut().wrap = Some(false);
       +
       +                        for i in 0..self.fonts.len() {
       +                            if ui
       +                                .selectable_value(&mut self.selected_font, i, &self.fonts[i].name)
       +                                .clicked()
       +                            {
                                        self.save_old_selected_char();
       -                                self.selected_font = select_font;
       +                                self.selected_font = i;
                                        self.old_selected_char_opt = None;
                                        self.selected_char_opt = None;
                                        self.show_selected_char();
                                    }
       -                        });
       +                        }
       +                    });
                        }
       +                ui.separator();
        
                        ui.horizontal(|ui| {
       -                    if ui.button("+").clicked() {
       +                    /*if ui.button("+").clicked() {
                                self.fonts.push(TheDrawFont::new(
                                    "New Font",
                                    icy_engine::FontType::Color,
       @@ -135,9 +148,12 @@ impl Document for CharFontEditor {
                                self.old_selected_char_opt = None;
                                self.show_selected_char();
                                self.undostack_len += 1;
       -                    }
       +                    }*/
        
       -                    if ui.button("🗑").clicked() {
       +                    if ui
       +                        .add_enabled(self.fonts.len() > 1, Button::new("🗑"))
       +                        .clicked()
       +                    {
                                self.fonts.remove(self.selected_font);
                                self.selected_font = 0;
                                self.selected_char_opt = None;
       @@ -146,7 +162,10 @@ impl Document for CharFontEditor {
                                self.undostack_len += 1;
                            }
        
       -                    if ui.button("Clone").clicked() {
       +                    if ui
       +                        .button(fl!(crate::LANGUAGE_LOADER, "tdf-editor-clone_button"))
       +                        .clicked()
       +                    {
                                self.fonts.push(self.fonts[self.selected_font].clone());
                                self.selected_font = self.fonts.len() - 1;
                                self.selected_char_opt = None;
       @@ -160,21 +179,50 @@ impl Document for CharFontEditor {
                TopBottomPanel::top("char_top_panel")
                    .exact_height(60.)
                    .show_inside(ui, |ui| {
       +                ui.add_space(4.0);
                        if self.selected_font < self.fonts.len() {
       -                    ui.horizontal(|ui| {
       -                        ui.label("Font Name:");
       +                    egui::Grid::new(
       +                        "font_grid
       +                    ",
       +                    )
       +                    .num_columns(4)
       +                    .spacing([4.0, 4.0])
       +                    .show(ui, |ui| {
       +                        ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
       +                            ui.label(fl!(crate::LANGUAGE_LOADER, "tdf-editor-font_name_label"));
       +                        });
                                if ui
                                    .add(
                                        TextEdit::singleline(&mut self.fonts[self.selected_font].name)
       +                                    .min_size(Vec2::new(200.0, 22.))
                                            .char_limit(12),
                                    )
                                    .changed()
                                {
                                    self.undostack_len += 1;
                                }
       -                    });
       -                    ui.horizontal(|ui| {
       -                        ui.label("Spacing:");
       +
       +                        ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
       +                            ui.label(fl!(crate::LANGUAGE_LOADER, "tdf-editor-font_type_label"));
       +                        });
       +
       +                        let text = match self.fonts[self.selected_font].font_type {
       +                            icy_engine::FontType::Outline => {
       +                                fl!(crate::LANGUAGE_LOADER, "tdf-editor-font_type_outline")
       +                            }
       +                            icy_engine::FontType::Block => {
       +                                fl!(crate::LANGUAGE_LOADER, "tdf-editor-font_type_block")
       +                            }
       +                            icy_engine::FontType::Color => {
       +                                fl!(crate::LANGUAGE_LOADER, "tdf-editor-font_type_color")
       +                            }
       +                        };
       +                        ui.label(text);
       +
       +                        ui.end_row();
       +                        ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
       +                            ui.label(fl!(crate::LANGUAGE_LOADER, "tdf-editor-spacing_label"));
       +                        });
                                if ui
                                    .add(
                                        egui::DragValue::new(&mut self.fonts[self.selected_font].spaces)
       @@ -184,9 +232,15 @@ impl Document for CharFontEditor {
                                {
                                    self.undostack_len += 1;
                                }
       +                        ui.label("");
       +                        ui.label("");
       +                        ui.end_row();
                            });
                        } else {
       -                    ui.heading("No font selected");
       +                    ui.heading(fl!(
       +                        crate::LANGUAGE_LOADER,
       +                        "tdf-editor-no_font_selected_label"
       +                    ));
                        }
                    });
        
       @@ -195,8 +249,12 @@ impl Document for CharFontEditor {
                    .show_inside(ui, |ui| {
                        if self.selected_font < self.fonts.len() {
                            self.show_char_selector(ui);
       -
       -                    if self.selected_char_opt.is_some() && ui.button("Clear char").clicked() {
       +                    ui.add_space(4.0);
       +                    if self.selected_char_opt.is_some()
       +                        && ui
       +                            .button(fl!(crate::LANGUAGE_LOADER, "tdf-editor-clear_char_button"))
       +                            .clicked()
       +                    {
                                self.fonts[self.selected_font].clear_glyph(self.selected_char_opt.unwrap());
                                self.selected_char_opt = None;
                                self.old_selected_char_opt = None;
       @@ -216,7 +274,7 @@ impl Document for CharFontEditor {
                            .get_caret()
                            .get_attribute();
        
       -                let mut show_color = false;
       +                let mut is_outline = false;
                        for layer in &mut self
                            .ansi_editor
                            .buffer_view
       @@ -226,22 +284,127 @@ impl Document for CharFontEditor {
                            .layers
                        {
                            match self.fonts[self.selected_font].font_type {
       -                        icy_engine::FontType::Outline | icy_engine::FontType::Block => {
       +                        icy_engine::FontType::Outline => {
       +                            layer.forced_output_attribute = Some(attr);
       +                            is_outline = true;
       +                        }
       +                        icy_engine::FontType::Block => {
                                    layer.forced_output_attribute = Some(attr);
                                }
                                icy_engine::FontType::Color => {
                                    layer.forced_output_attribute = None;
       -                            show_color = true;
                                }
                            }
                        }
        
       -                self.ansi_editor.buffer_view.lock().use_bg = show_color;
       -                self.ansi_editor.buffer_view.lock().use_fg = show_color;
       -                self.ansi_editor
       -                    .show_ui(ui, cur_tool, selected_tool, options);
       +                if is_outline {
       +                    SidePanel::right("outline…_side_panel")
       +                        .default_width(290.)
       +                        .show_inside(ui, |ui| {
       +                            TopBottomPanel::bottom("outline_style_bottom_panel")
       +                                .exact_height(220.)
       +                                .show_inside(ui, |ui| {
       +                                    self.outline_selection.show_outline_ui(ui, 8, Vec2::new(4.0, 4.0));
       +                                    let outline_style = self.outline_selection.get_outline_style();
       +                                    let old_style = self.outline_previewbuffer_view.lock().get_edit_state_mut().get_outline_style();
       +                                    self.outline_previewbuffer_view.lock().get_edit_state_mut().set_outline_style(outline_style);
       +                                    if outline_style != old_style {
       +                                        self.show_selected_char();
       +                                    }
       +                                });
       +
       +                            let opt = icy_engine_egui::TerminalOptions {
       +                                focus_lock: false,
       +                                stick_to_bottom: false,
       +                                scale: Some(Vec2::new(2.0, 2.0)),
       +                                settings: MonitorSettings {
       +                                    ..Default::default()
       +                                },
       +                                id: Some(egui::Id::new(self.id + 20000)),
       +                                ..Default::default()
       +                            };
       +
       +                            self.outline_previewbuffer_view
       +                                .lock()
       +                                .get_caret_mut()
       +                                .is_visible = false;
       +                            ui.horizontal(|ui|  {
       +                                ui.label(fl!(crate::LANGUAGE_LOADER, "tdf-editor-outline_preview_label"));
       +                                ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
       +                                    if ui.checkbox(&mut self.draw_outline_bg, fl!(crate::LANGUAGE_LOADER, "tdf-editor-draw_bg_checkbox")).changed() {
       +                                        self.render_outline_preview();
       +                                    }
       +                                });
       +                            });
       +                            let (_, _) = show_terminal_area(
       +                                ui,
       +                                self.outline_previewbuffer_view.clone(),
       +                                opt,
       +                            );
       +                        });
       +
       +
       +                    TopBottomPanel::top("cheat_sheet_top_panel")
       +                    .exact_height(50.)
       +                    .show_inside(ui, |ui| {
       +                        if self.opt_cheat_sheet.is_none() {
       +                            let mut buffer = Buffer::new((59, 3));
       +                            let s  = " Key: F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 1 2  3  4  5  6  7  8 ";
       +                            let s2 = "Code: A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  @  &  \u{F7} ";
       +                            let s3 = " Res: \u{CD}  \u{C4}  \u{B3}  \u{BA}  \u{D5}  \u{BB}  \u{D5}  \u{BF}  \u{C8}  \u{BE}  \u{C0}  \u{BD}  \u{B5}  \u{C7}  SP    &  \u{F7}";
       +
       +                            let mut attr  = TextAttribute::default();
       +                            attr.set_foreground(0);
       +                            attr.set_background(4);
       +
       +                            for (i, c) in s.chars().enumerate() {
       +                                buffer.layers[0].set_char((i, 0), AttributedChar::new(c, attr));
       +                            }
       +
       +                            attr.set_foreground(15);
       +                            attr.set_background(4);
       +
       +                            for (i, c) in s2.chars().enumerate() {
       +                                buffer.layers[0].set_char((i, 1), AttributedChar::new(c, attr));
       +                            }
       +                            attr.set_foreground(15);
       +                            attr.set_background(0);
       +
       +                            for (i, c) in s3.chars().enumerate() {
       +                                buffer.layers[0].set_char((i, 2), AttributedChar::new(c, attr));
       +                            }
       +                            self.opt_cheat_sheet = Some(crate::create_retained_image(&buffer));
       +                        }
       +
       +                        if let Some(cheat_sheet) = & self.opt_cheat_sheet {
       +                            ui.vertical_centered(|ui| {
       +                            cheat_sheet.show(ui);
       +                            });
       +                        }
       +                    });
       +
       +                    egui::CentralPanel::default().show_inside(ui, |ui| {
       +                        self.ansi_editor
       +                            .show_ui(ui, cur_tool, selected_tool, options);
       +                    });
       +                } else {
       +                    self.ansi_editor
       +                        .show_ui(ui, cur_tool, selected_tool, options);
       +                }
                    }
                });
       +        let u = self
       +            .ansi_editor
       +            .buffer_view
       +            .lock()
       +            .get_edit_state()
       +            .undo_stack_len();
       +        if self.last_update_preview != u {
       +            self.last_update_preview = u;
       +            self.save_old_selected_char();
       +            self.render_outline_preview();
       +        }
       +
                None
            }
        
       @@ -265,7 +428,16 @@ impl CharFontEditor {
                set_up_layers(&mut buffer);
                let ansi_editor = AnsiEditor::new(gl, id, buffer);
        
       +        let mut buffer = Buffer::new(Size::new(30, 12));
       +        buffer.is_terminal_buffer = true;
       +        let outline_previewbuffer_view = Arc::new(Mutex::new(BufferView::from_buffer(
       +            gl,
       +            buffer,
       +            glow::NEAREST as i32,
       +        )));
       +
                let mut res = Self {
       +            id,
                    font: BitFont::default(),
                    ansi_editor,
                    selected_char_opt: Some('A'),
       @@ -273,6 +445,11 @@ impl CharFontEditor {
                    fonts,
                    selected_font: 0,
                    undostack_len: 0,
       +            outline_previewbuffer_view,
       +            outline_selection: SelectOutlineDialog::default(),
       +            last_update_preview: 0,
       +            opt_cheat_sheet: None,
       +            draw_outline_bg: true,
                };
                res.show_selected_char();
                res
       @@ -303,21 +480,74 @@ impl CharFontEditor {
                });
            }
        
       -    fn show_selected_char(&mut self) {
       -        println!("show selected!");
       -        self.save_old_selected_char();
       +    fn render_outline_preview(&mut self) {
       +        let font = &self.fonts[self.selected_font];
       +        if matches!(font.font_type, icy_engine::FontType::Outline) {
       +            let lock = &mut self.ansi_editor.buffer_view.lock();
       +            let mut attr = lock.get_caret().get_attribute();
       +
       +            let _ = self
       +                .outline_previewbuffer_view
       +                .lock()
       +                .get_edit_state_mut()
       +                .clear_layer(0);
       +            self.outline_previewbuffer_view
       +                .lock()
       +                .get_caret_mut()
       +                .set_attr(attr);
       +            if let Some(ch) = self.selected_char_opt {
       +                let size = self
       +                    .outline_previewbuffer_view
       +                    .lock()
       +                    .get_edit_state_mut()
       +                    .get_buffer()
       +                    .get_size();
       +
       +                if self.draw_outline_bg {
       +                    attr.set_foreground(8);
       +                    attr.set_background(0);
       +                    for y in 0..size.height {
       +                        for x in 0..size.width {
       +                            self.outline_previewbuffer_view
       +                                .lock()
       +                                .get_edit_state_mut()
       +                                .get_buffer_mut()
       +                                .layers[0]
       +                                .set_char((x, y), AttributedChar::new('\u{B1}', attr));
       +                        }
       +                    }
       +                }
        
       -        let lock = &mut self.ansi_editor.buffer_view.lock();
       -        let edit_state = &mut lock.get_edit_state_mut();
       -        set_up_layers(edit_state.get_buffer_mut());
       -        edit_state.set_current_layer(1);
       -        edit_state.get_caret_mut().set_position((0, 0).into());
       -        if let Some(ch) = self.selected_char_opt {
       +                font.render(
       +                    self.outline_previewbuffer_view.lock().get_edit_state_mut(),
       +                    ch as u8,
       +                );
       +            }
       +        }
       +    }
       +
       +    fn show_selected_char(&mut self) {
       +        {
       +            self.save_old_selected_char();
                    let font = &self.fonts[self.selected_font];
       -            font.render(edit_state, ch as u8);
       +            self.ansi_editor.outline_font_mode =
       +                matches!(font.font_type, icy_engine::FontType::Outline);
       +            let lock = &mut self.ansi_editor.buffer_view.lock();
       +
       +            let edit_state = &mut lock.get_edit_state_mut();
       +            set_up_layers(edit_state.get_buffer_mut());
       +            edit_state.set_current_layer(1);
       +            edit_state.get_caret_mut().set_position((0, 0).into());
       +            edit_state.set_outline_style(usize::MAX);
       +
       +            if let Some(ch) = self.selected_char_opt {
       +                font.render(edit_state, ch as u8);
       +            }
       +
       +            edit_state.get_undo_stack().lock().unwrap().clear();
       +            self.old_selected_char_opt = self.selected_char_opt;
                }
       -        edit_state.get_undo_stack().lock().unwrap().clear();
       -        self.old_selected_char_opt = self.selected_char_opt;
       +        self.render_outline_preview();
            }
        
            fn save_old_selected_char(&mut self) {
       @@ -336,7 +566,33 @@ impl CharFontEditor {
                    if let Some(ch) = self.old_selected_char_opt {
                        match font.font_type {
                            icy_engine::FontType::Outline => {
       -                        log::warn!("TODO: save old selected char for outline fonts");
       +                        let lock = &mut self.ansi_editor.buffer_view.lock();
       +                        let buf = lock.get_buffer();
       +                        let mut data = Vec::new();
       +                        let mut w = 0;
       +                        let mut h = 0;
       +                        for y in 0..buf.get_line_count() {
       +                            if y > 0 {
       +                                data.push(13);
       +                            }
       +                            let lw = buf.get_line_length(y);
       +                            for x in 0..lw {
       +                                let ch = buf.get_char((x, y));
       +                                if VALID_OUTLINE_CHARS.contains(ch.ch) {
       +                                    data.push(ch.ch as u8);
       +                                }
       +                            }
       +                            w = w.max(lw);
       +                            h = y;
       +                        }
       +
       +                        font.set_glyph(
       +                            ch,
       +                            FontGlyph {
       +                                size: Size::new(w, h),
       +                                data,
       +                            },
       +                        );
                            }
                            icy_engine::FontType::Block => {
                                let lock = &mut self.ansi_editor.buffer_view.lock();