brush_imp.rs - 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
---
brush_imp.rs (10132B)
---
1 use eframe::egui::Response;
2 use egui::{load::SizedTexture, Image, TextureHandle, Widget};
3 use i18n_embed_fl::fl;
4 use icy_engine::{editor::AtomicUndoGuard, AttributedChar, Layer, TextPane};
5 use icy_engine_egui::TerminalCalc;
6 use std::{cell::RefCell, rc::Rc};
7
8 use crate::{
9 create_image,
10 paint::{BrushMode, ColorMode},
11 AnsiEditor, Event, Message,
12 };
13
14 use super::{Position, Tool};
15
16 pub static mut CUSTOM_BRUSH: Option<Layer> = None;
17
18 pub struct BrushTool {
19 color_mode: ColorMode,
20 size: i32,
21 char_code: Rc<RefCell<char>>,
22
23 undo_op: Option<AtomicUndoGuard>,
24 cur_pos: Position,
25 custom_brush: Option<Layer>,
26 image: Option<TextureHandle>,
27 brush_mode: BrushMode,
28 }
29
30 impl Default for BrushTool {
31 fn default() -> Self {
32 Self {
33 size: 3,
34 color_mode: ColorMode::Both,
35 undo_op: None,
36 custom_brush: None,
37 image: None,
38 brush_mode: BrushMode::Shade,
39 char_code: Rc::new(RefCell::new('\u{00B0}')),
40 cur_pos: Position::default(),
41 }
42 }
43 }
44 impl BrushTool {
45 fn paint_brush(&self, editor: &mut AnsiEditor, pos: Position) {
46 let mid = Position::new(-(self.size / 2), -(self.size / 2));
47
48 let center = pos + mid;
49 let gradient = ['\u{00B0}', '\u{00B1}', '\u{00B2}', '\u{00DB}'];
50 let caret_attr = editor.buffer_view.lock().get_caret().get_attribute();
51 if matches!(self.brush_mode, BrushMode::Custom) {
52 editor.join_overlay("brush");
53 return;
54 }
55
56 let use_selection = editor.buffer_view.lock().get_edit_state().is_something_selected();
57 editor.buffer_view.lock().get_edit_state_mut().set_is_buffer_dirty();
58
59 let offset = if let Some(layer) = editor.buffer_view.lock().get_edit_state().get_cur_layer() {
60 layer.get_offset()
61 } else {
62 Position::default()
63 };
64
65 for y in 0..self.size {
66 for x in 0..self.size {
67 let pos = center + Position::new(x, y);
68 if use_selection && !editor.buffer_view.lock().get_edit_state().get_is_selected(pos + offset) {
69 continue;
70 }
71 let ch = editor.get_char_from_cur_layer(pos);
72 let mut attribute = ch.attribute;
73 attribute.attr &= !icy_engine::attribute::INVISIBLE;
74
75 if self.color_mode.use_fore() {
76 attribute.set_foreground(caret_attr.get_foreground());
77 }
78 if self.color_mode.use_back() {
79 attribute.set_background(caret_attr.get_background());
80 }
81
82 match &self.brush_mode {
83 BrushMode::Shade => {
84 let mut char_code = gradient[0];
85 if ch.ch == gradient[gradient.len() - 1] {
86 char_code = gradient[gradient.len() - 1];
87 } else {
88 for i in 0..gradient.len() - 1 {
89 if ch.ch == gradient[i] {
90 char_code = gradient[i + 1];
91 break;
92 }
93 }
94 }
95 editor.set_char(pos, AttributedChar::new(char_code, attribute));
96 }
97 BrushMode::Char(ch) => {
98 attribute.set_font_page(caret_attr.get_font_page());
99 editor.set_char(center + Position::new(x, y), AttributedChar::new(*ch.borrow(), attribute));
100 }
101 BrushMode::Colorize => {
102 editor.set_char(pos, AttributedChar::new(ch.ch, attribute));
103 }
104 _ => {}
105 }
106 }
107 }
108 }
109 }
110
111 impl Tool for BrushTool {
112 fn get_icon(&self) -> &egui::Image<'static> {
113 &super::icons::BRUSH_SVG
114 }
115
116 fn tool_name(&self) -> String {
117 fl!(crate::LANGUAGE_LOADER, "tool-paint_brush_name")
118 }
119
120 fn tooltip(&self) -> String {
121 fl!(crate::LANGUAGE_LOADER, "tool-paint_brush_tooltip")
122 }
123
124 fn use_caret(&self, _editor: &AnsiEditor) -> bool {
125 false
126 }
127
128 fn show_ui(&mut self, ctx: &egui::Context, ui: &mut egui::Ui, editor_opt: Option<&mut AnsiEditor>) -> Option<Message> {
129 self.color_mode.show_ui(ui);
130
131 ui.horizontal(|ui| {
132 ui.label(fl!(crate::LANGUAGE_LOADER, "tool-size-label"));
133 ui.add(egui::DragValue::new(&mut self.size).clamp_range(1..=20).speed(1));
134 });
135 /*
136 ui.radio_value(&mut self.brush_type, BrushType::Shade, fl!(crate::LANGUAGE_LOADER, "tool-shade"));
137 ui.horizontal(|ui| {
138 ui.radio_value(&mut self.brush_type, BrushType::Solid, fl!(crate::LANGUAGE_LOADER, "tool-character"));
139 if let Some(buffer_opt) = buffer_opt {
140 result = draw_glyph(ui, buffer_opt, &self.char_code);
141 }
142 });
143 ui.radio_value(&mut self.brush_type, BrushType::Color, fl!(crate::LANGUAGE_LOADER, "tool-colorize"));
144 */
145 let result = self.brush_mode.show_ui(ui, editor_opt, self.char_code.clone(), crate::paint::BrushUi::Brush);
146
147 unsafe {
148 if CUSTOM_BRUSH.is_some() {
149 self.custom_brush = CUSTOM_BRUSH.take();
150 }
151 }
152
153 if let Some(custom_brush) = &self.custom_brush {
154 let mut layer = custom_brush.clone();
155 layer.set_offset((0, 0));
156 layer.role = icy_engine::Role::Normal;
157 let mut buf = icy_engine::Buffer::new(layer.get_size());
158 layer.set_title(buf.layers[0].get_title());
159 buf.layers.clear();
160 buf.layers.push(layer);
161 self.image = Some(create_image(ctx, &buf));
162
163 ui.radio_value(&mut self.brush_mode, BrushMode::Custom, fl!(crate::LANGUAGE_LOADER, "tool-custom-brush"));
164 if let Some(image) = &self.image {
165 let sized_texture: SizedTexture = image.into();
166 let w = ui.available_width() - 16.0;
167 let scale = w / sized_texture.size.x;
168 let image = Image::from_texture(sized_texture).fit_to_original_size(scale);
169 image.ui(ui);
170 }
171 }
172 result
173 }
174
175 fn handle_no_hover(&mut self, editor: &mut AnsiEditor) {
176 if matches!(self.brush_mode, BrushMode::Custom) {
177 editor.clear_overlay_layer();
178 }
179 let lock = &mut editor.buffer_view.lock();
180 let get_edit_state_mut = lock.get_edit_state_mut();
181 if get_edit_state_mut.get_tool_overlay_mask_mut().is_empty() {
182 return;
183 }
184 get_edit_state_mut.get_tool_overlay_mask_mut().clear();
185 get_edit_state_mut.set_is_buffer_dirty();
186 }
187
188 fn handle_hover(&mut self, _ui: &egui::Ui, response: egui::Response, editor: &mut AnsiEditor, cur: Position, cur_abs: Position) -> egui::Response {
189 if matches!(self.brush_mode, BrushMode::Custom) {
190 editor.clear_overlay_layer();
191 let lock = &mut editor.buffer_view.lock();
192 if let Some(layer) = lock.get_edit_state_mut().get_overlay_layer() {
193 if let Some(brush) = &self.custom_brush {
194 let mid = Position::new(-(brush.get_width() / 2), -(brush.get_height() / 2));
195 self.cur_pos = cur + mid;
196 for y in 0..brush.get_height() {
197 for x in 0..brush.get_width() {
198 let pos = Position::new(x, y);
199 let ch = brush.get_char(pos);
200 layer.set_char(cur + pos + mid, AttributedChar::new(ch.ch, ch.attribute));
201 }
202 }
203 lock.get_edit_state_mut().set_is_buffer_dirty();
204 }
205 }
206 } else {
207 let mid = Position::new(-(self.size / 2), -(self.size / 2));
208
209 if self.cur_pos != cur + mid {
210 self.cur_pos = cur + mid;
211 let lock = &mut editor.buffer_view.lock();
212 let get_tool_overlay_mask_mut = lock.get_edit_state_mut().get_tool_overlay_mask_mut();
213 get_tool_overlay_mask_mut.clear();
214 for y in 0..self.size {
215 for x in 0..self.size {
216 let pos = cur_abs + Position::new(x, y) + mid;
217 get_tool_overlay_mask_mut.set_is_selected(pos, true);
218 }
219 }
220 lock.get_edit_state_mut().set_is_buffer_dirty();
221 }
222 editor.buffer_view.lock().get_buffer_mut().remove_overlay();
223 }
224 response.on_hover_cursor(egui::CursorIcon::Crosshair)
225 }
226
227 fn handle_click(&mut self, editor: &mut AnsiEditor, button: i32, pos: Position, _pos_abs: Position, _response: &Response) -> Option<Message> {
228 if button == 1 {
229 let _op: AtomicUndoGuard = editor.begin_atomic_undo(fl!(crate::LANGUAGE_LOADER, "undo-paint-brush"));
230
231 self.paint_brush(editor, pos);
232 }
233 None
234 }
235
236 fn handle_drag(&mut self, _ui: &egui::Ui, response: egui::Response, editor: &mut AnsiEditor, _calc: &TerminalCalc) -> egui::Response {
237 self.paint_brush(editor, editor.drag_pos.cur);
238 response
239 }
240
241 fn handle_drag_begin(&mut self, editor: &mut AnsiEditor, _response: &egui::Response) -> Event {
242 self.undo_op = Some(editor.begin_atomic_undo(fl!(crate::LANGUAGE_LOADER, "undo-paint-brush")));
243 self.paint_brush(editor, editor.drag_pos.cur);
244 Event::None
245 }
246
247 fn handle_drag_end(&mut self, _editor: &mut AnsiEditor) -> Option<Message> {
248 self.undo_op = None;
249 None
250 }
251
252 fn get_toolbar_location_text(&self, _editor: &AnsiEditor) -> String {
253 let pos = self.cur_pos;
254 fl!(crate::LANGUAGE_LOADER, "toolbar-position", line = (pos.y + 1), column = (pos.x + 1))
255 }
256 }