fill_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
---
fill_imp.rs (6299B)
---
1 use std::{cell::RefCell, collections::HashSet, rc::Rc};
2
3 use eframe::egui;
4 use i18n_embed_fl::fl;
5 use icy_engine::{AttributedChar, Size, TextPane};
6
7 use crate::{
8 paint::{BrushMode, ColorMode},
9 AnsiEditor, Message,
10 };
11
12 use super::{Position, Tool};
13
14 pub struct FillTool {
15 color_mode: ColorMode,
16
17 char_code: std::rc::Rc<std::cell::RefCell<char>>,
18 fill_type: BrushMode,
19 use_exact_matching: bool,
20 }
21
22 impl FillTool {
23 pub fn new() -> Self {
24 let c = Rc::new(RefCell::new('\u{00B0}'));
25 Self {
26 color_mode: ColorMode::Both,
27 char_code: c.clone(),
28 fill_type: BrushMode::Char(c),
29 use_exact_matching: false,
30 }
31 }
32 }
33 #[allow(clippy::struct_excessive_bools)]
34 struct FillOperation {
35 fill_type: BrushMode,
36 color_mode: ColorMode,
37 use_exact_matching: bool,
38
39 size: Size,
40 pub offset: Position,
41 use_selection: bool,
42 base_char: AttributedChar,
43 new_char: AttributedChar,
44 visited: HashSet<Position>,
45 }
46
47 impl FillOperation {
48 pub fn new(fill_tool: &FillTool, editor: &AnsiEditor, base_char: AttributedChar, new_ch: AttributedChar) -> Self {
49 let lock = &editor.buffer_view.lock();
50 let state = lock.get_edit_state();
51 let size = state.get_cur_layer().unwrap().get_size();
52 let use_selection = state.is_something_selected();
53 let offset = if let Some(layer) = state.get_cur_layer() {
54 layer.get_offset()
55 } else {
56 Position::default()
57 };
58
59 Self {
60 size,
61 color_mode: fill_tool.color_mode,
62 fill_type: fill_tool.fill_type.clone(),
63 use_selection,
64 base_char,
65 offset,
66 new_char: new_ch,
67 use_exact_matching: fill_tool.use_exact_matching,
68 visited: HashSet::new(),
69 }
70 }
71
72 pub fn fill(&mut self, editor: &mut AnsiEditor, pos: Position) {
73 let mut pos_stack = vec![pos];
74
75 while let Some(pos) = pos_stack.pop() {
76 if pos.x < 0 || pos.y < 0 || pos.x >= self.size.width || pos.y >= self.size.height || !self.visited.insert(pos) {
77 continue;
78 }
79
80 if !self.use_selection || editor.buffer_view.lock().get_edit_state().get_is_selected(pos + self.offset) {
81 let cur_char = editor.buffer_view.lock().get_edit_state().get_cur_layer().unwrap().get_char(pos);
82
83 let mut repl_ch = cur_char;
84
85 match &self.fill_type {
86 BrushMode::Char(_) => {
87 if self.use_exact_matching && cur_char != self.base_char || !self.use_exact_matching && cur_char.ch != self.base_char.ch {
88 continue;
89 }
90 repl_ch.ch = self.new_char.ch;
91 repl_ch.set_font_page(self.new_char.get_font_page());
92 }
93 BrushMode::Colorize => {
94 if self.use_exact_matching && cur_char != self.base_char || !self.use_exact_matching && cur_char.attribute != self.base_char.attribute {
95 continue;
96 }
97 }
98 _ => {}
99 }
100 if self.color_mode.use_fore() {
101 repl_ch.attribute.set_foreground(self.new_char.attribute.get_foreground());
102 repl_ch.attribute.set_is_bold(self.new_char.attribute.is_bold());
103 }
104
105 if self.color_mode.use_back() {
106 repl_ch.attribute.set_background(self.new_char.attribute.get_background());
107 }
108
109 repl_ch.set_font_page(editor.buffer_view.lock().get_caret().get_attribute().get_font_page());
110 repl_ch.attribute.attr &= !icy_engine::attribute::INVISIBLE;
111 editor.set_char(pos, repl_ch);
112 }
113
114 pos_stack.push(pos + Position::new(-1, 0));
115 pos_stack.push(pos + Position::new(1, 0));
116 pos_stack.push(pos + Position::new(0, -1));
117 pos_stack.push(pos + Position::new(0, 1));
118 }
119 }
120 }
121
122 // Fill with
123 // Attribute, Fore/Back
124 // Character
125 // Both
126 impl Tool for FillTool {
127 fn get_icon(&self) -> &egui::Image<'static> {
128 &super::icons::FILL_SVG
129 }
130
131 fn tool_name(&self) -> String {
132 fl!(crate::LANGUAGE_LOADER, "tool-fill_name")
133 }
134
135 fn tooltip(&self) -> String {
136 fl!(crate::LANGUAGE_LOADER, "tool-fill_tooltip")
137 }
138
139 fn use_caret(&self, _editor: &AnsiEditor) -> bool {
140 false
141 }
142 fn show_ui(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui, editor_opt: Option<&mut AnsiEditor>) -> Option<Message> {
143 self.color_mode.show_ui(ui);
144
145 ui.checkbox(&mut self.use_exact_matching, fl!(crate::LANGUAGE_LOADER, "tool-fill-exact_match_label"));
146
147 self.fill_type.show_ui(ui, editor_opt, self.char_code.clone(), crate::paint::BrushUi::Fill)
148 }
149
150 fn handle_hover(&mut self, _ui: &egui::Ui, response: egui::Response, _editor: &mut AnsiEditor, _cur: Position, _cur_abs: Position) -> egui::Response {
151 response.on_hover_cursor(egui::CursorIcon::Crosshair)
152 }
153
154 fn handle_click(&mut self, editor: &mut AnsiEditor, button: i32, pos: Position, _pos_abs: Position, _response: &egui::Response) -> Option<Message> {
155 if button == 1 {
156 let Ok(layer) = editor.get_cur_layer_index() else { return None };
157 if layer >= editor.buffer_view.lock().get_buffer().layers.len() {
158 return None;
159 }
160 let attr = editor.buffer_view.lock().get_caret().get_attribute();
161 let ch = if let Some(layer) = editor.buffer_view.lock().get_edit_state().get_cur_layer() {
162 layer.get_char(pos)
163 } else {
164 return None;
165 };
166 if self.color_mode.use_fore() || self.color_mode.use_back() || matches!(self.fill_type, BrushMode::Char(_)) {
167 let _undo = editor.begin_atomic_undo(fl!(crate::LANGUAGE_LOADER, "undo-bucket-fill"));
168 let mut op = FillOperation::new(self, editor, ch, AttributedChar::new(*self.char_code.borrow(), attr));
169 op.fill(editor, pos);
170 }
171 }
172 None
173 }
174 }