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(