├── .gitignore ├── COPYING ├── Cargo.toml ├── README.md ├── _test.sh ├── examples ├── blockcode.rs ├── demo.rs ├── hex_test.png └── list_test.rs ├── res ├── dejavu-fonts-ttf-2.37.zip └── gui_mockup.svg ├── rustfmt.toml └── src ├── blocklang.rs ├── font.ttf ├── font_mono.ttf ├── layout.rs ├── lib.rs ├── markdown.rs ├── painter.rs ├── rect.rs ├── style.rs ├── ui.rs ├── widget.rs ├── widget_store.rs ├── widgets ├── blockcode.rs ├── connector.rs ├── entry.rs ├── graph.rs ├── graph_minmax.rs ├── hexgrid.rs ├── hexknob.rs ├── list.rs ├── mod.rs ├── octave_keys.rs ├── pattern_editor.rs ├── scope.rs └── wichtext.rs └── window.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.history 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hexotk" 3 | version = "0.5.1" 4 | authors = ["Weird Constructor "] 5 | edition = "2021" 6 | license = "GPL-3.0-or-later" 7 | description = "HexoTK - A GUI Toolkit for Audio Plugins" 8 | 9 | [lib] 10 | name = "hexotk" 11 | 12 | [features] 13 | default=[] 14 | driver=[] 15 | 16 | [dependencies] 17 | hexodsp = { git = "https://github.com/WeirdConstructor/HexoDSP.git" } 18 | #hexodsp = { path = "../hexodsp" } 19 | femtovg = { git = "https://github.com/femtovg/femtovg", default-features = false, features = ["image-loading"] } 20 | #femtovg = { path = "../other/femtovg", features = ["image-loading"] } 21 | image = { version = "0.24.3", features = ["png", "jpeg"] } 22 | raw-gl-context = { git = "https://github.com/glowcoil/raw-gl-context" } 23 | morphorm = { git = "https://github.com/WeirdConstructor/morphorm.git" } 24 | #morphorm = { path = "../morphorm" } 25 | baseview = { git = "https://github.com/RustAudio/baseview.git", features = ["opengl"] } 26 | keyboard-types = { version = "0.6.1", default-features = false } 27 | pulldown-cmark = "0.9.2" 28 | raw-window-handle = "0.5.0" 29 | gl = "0.14.0" 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HexoTK - A graphic user interface toolkit for audio plugins 2 | 3 | ## State of Development 4 | 5 | Super early! 6 | 7 | ## Building 8 | 9 | cargo run --example demo 10 | 11 | 12 | ## TODO / Features 13 | 14 | * Everything 15 | 16 | ## Known Bugs 17 | 18 | * The ones you encounter and create as issues on GitHub. 19 | 20 | ## Support Development 21 | 22 | You can support me (and the development of this project) via Liberapay: 23 | 24 | Donate using Liberapay 25 | 26 | ## License 27 | 28 | This project is licensed under the GNU Affero General Public License Version 3 or 29 | later. 30 | 31 | The DSP code that was partially translated from LMMS C++ to Rust and was 32 | originally released under GNU General Public License Version 2 or any later. 33 | The former authors were: 34 | 35 | * Copyright (c) 2006-2014 Tobias Doerffel 36 | * Copyright (c) 2014 grejppi 37 | 38 | The fonts DejaVuSerif.ttf and DejaVuSansMono.ttf under the license: 39 | 40 | Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. 41 | Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) 42 | 43 | ### Why GPL? 44 | 45 | Picking a license for my code bothered me for a long time. I read many 46 | discussions about this topic. Read the license explanations. And discussed 47 | this matter with other developers. 48 | 49 | First about _why I write code for free_ at all, the reasons are: 50 | 51 | - It's my passion to write computer programs. In my free time I can 52 | write the code I want, when I want and the way I want. I can freely 53 | allocate my time and freely choose the projects I want to work on. 54 | - To help a friend or member of my family. 55 | - To solve a problem I have. 56 | - To learn something new. 57 | 58 | Those are the reasons why I write code for free. Now the reasons 59 | _why I publish the code_, when I could as well keep it to myself: 60 | 61 | - So that it may bring value to users and the free software community. 62 | - Show my work as an artist. 63 | - To get into contact with other developers. 64 | - To exchange knowledge and help other developers. 65 | - And it's a nice change to put some more polish on my private projects. 66 | 67 | Most of those reasons don't yet justify GPL. The main point of the GPL, as far 68 | as I understand: The GPL makes sure the software stays free software until 69 | eternity. That the _end user_ of the software always stays in control. That the users 70 | have the means to adapt the software to new platforms or use cases. 71 | Even if the original authors don't maintain the software anymore. 72 | It ultimately prevents _"vendor lock in"_. I really dislike vendor lock in, 73 | especially as developer. Especially as developer I want and need to stay 74 | in control of the computers and software I use. 75 | 76 | Another point is, that my work (and the work of any other developer) has a 77 | value. If I give away my work without _any_ strings attached, I effectively 78 | work for free. This compromises the price I (and potentially other developers) 79 | can demand for the skill, workforce and time. 80 | 81 | This makes two reasons for me to choose the GPL: 82 | 83 | 1. I do not want to support vendor lock in scenarios for free. 84 | I want to prevent those when I have a choice, when I invest my private 85 | time to bring value to the end users. 86 | 2. I don't want to low ball my own (and other developer's) wage and prices 87 | by giving away the work I spent my scarce private time on with no strings 88 | attached. I do not want companies to be able to use it in closed source 89 | projects to drive a vendor lock in scenario. 90 | 91 | We can discuss relicensing of my code or project if you are interested in using 92 | it in a closed source project. Bear in mind, that I can only relicense the 93 | parts of the project I wrote. If the project contains GPL code from other 94 | projects and authors, I can't relicense it. 95 | -------------------------------------------------------------------------------- /_test.sh: -------------------------------------------------------------------------------- 1 | cargo run --release --color=always --example demo 2>&1 | less -R 2 | -------------------------------------------------------------------------------- /examples/blockcode.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Weird Constructor 2 | // This file is a part of HexoTK. Released under GPL-3.0-or-later. 3 | // See README.md and COPYING for details. 4 | 5 | use hexodsp::wblockdsp::*; 6 | use hexotk::*; 7 | use std::cell::RefCell; 8 | use std::rc::Rc; 9 | use std::sync::{Arc, Mutex}; 10 | 11 | const WINDOW_W: i32 = 1020; 12 | const WINDOW_H: i32 = 720; 13 | 14 | #[derive(Debug)] 15 | pub struct ASTNode { 16 | pub id: usize, 17 | pub typ: String, 18 | pub lbl: String, 19 | pub nodes: Vec<(String, String, ASTNodeRef)>, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub struct ASTNodeRef(Rc>); 24 | 25 | impl ASTNodeRef { 26 | pub fn walk_dump(&self, input: &str, output: &str, indent: usize) -> String { 27 | let indent_str = " ".repeat(indent + 1); 28 | 29 | let out_port = if output.len() > 0 { format!("(out: {})", output) } else { "".to_string() }; 30 | let in_port = if input.len() > 0 { format!("(in: {})", input) } else { "".to_string() }; 31 | 32 | let mut s = format!( 33 | "{}{}#{}[{}] {}{}\n", 34 | indent_str, 35 | self.0.borrow().id, 36 | self.0.borrow().typ, 37 | self.0.borrow().lbl, 38 | out_port, 39 | in_port 40 | ); 41 | 42 | for (inp, out, n) in &self.0.borrow().nodes { 43 | s += &n.walk_dump(&inp, &out, indent + 1); 44 | } 45 | 46 | s 47 | } 48 | } 49 | 50 | impl BlockASTNode for ASTNodeRef { 51 | fn from(id: usize, typ: &str, lbl: &str) -> ASTNodeRef { 52 | ASTNodeRef(Rc::new(RefCell::new(ASTNode { 53 | id, 54 | typ: typ.to_string(), 55 | lbl: lbl.to_string(), 56 | nodes: vec![], 57 | }))) 58 | } 59 | 60 | fn add_node(&self, in_port: String, out_port: String, node: ASTNodeRef) { 61 | self.0.borrow_mut().nodes.push((in_port, out_port, node)); 62 | } 63 | } 64 | 65 | fn make_block_lang() -> Rc> { 66 | let mut lang = BlockLanguage::new(); 67 | 68 | lang.define(BlockType { 69 | category: "source".to_string(), 70 | name: "phse".to_string(), 71 | rows: 1, 72 | inputs: vec![Some("f".to_string())], 73 | outputs: vec![Some("".to_string())], 74 | area_count: 0, 75 | user_input: BlockUserInput::None, 76 | description: 77 | "A phasor, returns a saw tooth wave to scan through things or use as modulator." 78 | .to_string(), 79 | color: 2, 80 | }); 81 | 82 | lang.define(BlockType { 83 | category: "literals".to_string(), 84 | name: "zero".to_string(), 85 | rows: 1, 86 | inputs: vec![], 87 | outputs: vec![Some("".to_string())], 88 | area_count: 0, 89 | user_input: BlockUserInput::None, 90 | description: "The 0.0 value".to_string(), 91 | color: 1, 92 | }); 93 | 94 | lang.define(BlockType { 95 | category: "routing".to_string(), 96 | name: "->".to_string(), 97 | rows: 1, 98 | inputs: vec![Some("".to_string())], 99 | outputs: vec![Some("".to_string())], 100 | area_count: 0, 101 | user_input: BlockUserInput::None, 102 | description: "Forwards the value one block".to_string(), 103 | color: 6, 104 | }); 105 | 106 | lang.define(BlockType { 107 | category: "routing".to_string(), 108 | name: "->2".to_string(), 109 | rows: 2, 110 | inputs: vec![Some("".to_string())], 111 | outputs: vec![Some("".to_string()), Some("".to_string())], 112 | area_count: 0, 113 | user_input: BlockUserInput::None, 114 | description: "Forwards the value one block and sends it to multiple destinations" 115 | .to_string(), 116 | color: 6, 117 | }); 118 | 119 | lang.define(BlockType { 120 | category: "routing".to_string(), 121 | name: "->3".to_string(), 122 | rows: 3, 123 | inputs: vec![Some("".to_string())], 124 | outputs: vec![Some("".to_string()), Some("".to_string()), Some("".to_string())], 125 | area_count: 0, 126 | user_input: BlockUserInput::None, 127 | description: "Forwards the value one block and sends it to multiple destinations" 128 | .to_string(), 129 | color: 6, 130 | }); 131 | 132 | lang.define(BlockType { 133 | category: "variables".to_string(), 134 | name: "set".to_string(), 135 | rows: 1, 136 | inputs: vec![Some("".to_string())], 137 | outputs: vec![], 138 | area_count: 0, 139 | user_input: BlockUserInput::Identifier, 140 | description: "Stores into a variable".to_string(), 141 | color: 2, 142 | }); 143 | 144 | lang.define(BlockType { 145 | category: "variables".to_string(), 146 | name: "get".to_string(), 147 | rows: 1, 148 | inputs: vec![], 149 | outputs: vec![Some("".to_string())], 150 | area_count: 0, 151 | user_input: BlockUserInput::Identifier, 152 | description: "Loads a variable".to_string(), 153 | color: 12, 154 | }); 155 | 156 | lang.define(BlockType { 157 | category: "variables".to_string(), 158 | name: "if".to_string(), 159 | rows: 1, 160 | inputs: vec![Some("".to_string())], 161 | outputs: vec![Some("".to_string())], 162 | area_count: 2, 163 | user_input: BlockUserInput::None, 164 | description: "Divides the controlflow based on a true (>= 0.5) \ 165 | or false (< 0.5) input value." 166 | .to_string(), 167 | color: 0, 168 | }); 169 | 170 | lang.define(BlockType { 171 | category: "nodes".to_string(), 172 | name: "1pole".to_string(), 173 | rows: 2, 174 | inputs: vec![Some("in".to_string()), Some("f".to_string())], 175 | outputs: vec![Some("lp".to_string()), Some("hp".to_string())], 176 | area_count: 0, 177 | user_input: BlockUserInput::None, 178 | description: "Runs a simple one pole filter on the input".to_string(), 179 | color: 8, 180 | }); 181 | 182 | lang.define(BlockType { 183 | category: "nodes".to_string(), 184 | name: "svf".to_string(), 185 | rows: 3, 186 | inputs: vec![Some("in".to_string()), Some("f".to_string()), Some("r".to_string())], 187 | outputs: vec![Some("lp".to_string()), Some("bp".to_string()), Some("hp".to_string())], 188 | area_count: 0, 189 | user_input: BlockUserInput::None, 190 | description: "Runs a state variable filter on the input".to_string(), 191 | color: 8, 192 | }); 193 | 194 | lang.define(BlockType { 195 | category: "functions".to_string(), 196 | name: "sin".to_string(), 197 | rows: 1, 198 | inputs: vec![Some("".to_string())], 199 | outputs: vec![Some("".to_string())], 200 | area_count: 0, 201 | user_input: BlockUserInput::None, 202 | description: "Calculates the sine of the input".to_string(), 203 | color: 16, 204 | }); 205 | 206 | lang.define(BlockType { 207 | category: "nodes".to_string(), 208 | name: "delay".to_string(), 209 | rows: 2, 210 | inputs: vec![Some("in".to_string()), Some("t".to_string())], 211 | outputs: vec![Some("".to_string())], 212 | area_count: 0, 213 | user_input: BlockUserInput::None, 214 | description: "Runs a linearly interpolated delay on the input".to_string(), 215 | color: 8, 216 | }); 217 | 218 | for fun_name in &["+", "-", "*", "/"] { 219 | lang.define(BlockType { 220 | category: "arithmetics".to_string(), 221 | name: fun_name.to_string(), 222 | rows: 2, 223 | inputs: if fun_name == &"-" || fun_name == &"/" { 224 | vec![Some("a".to_string()), Some("b".to_string())] 225 | } else { 226 | vec![Some("".to_string()), Some("".to_string())] 227 | }, 228 | outputs: vec![Some("".to_string())], 229 | area_count: 0, 230 | user_input: BlockUserInput::None, 231 | description: "A binary arithmetics operation".to_string(), 232 | color: 4, 233 | }); 234 | } 235 | 236 | lang.define_identifier("alpha"); 237 | lang.define_identifier("beta"); 238 | lang.define_identifier("delta"); 239 | lang.define_identifier("gamma"); 240 | lang.define_identifier("&sig1"); 241 | lang.define_identifier("&sig2"); 242 | 243 | Rc::new(RefCell::new(lang)) 244 | } 245 | 246 | fn main() { 247 | let concurrent_data = Arc::new(Mutex::new(CloneMutable::new(("Count:".to_string(), 0)))); 248 | 249 | std::thread::spawn({ 250 | let data = concurrent_data.clone(); 251 | move || loop { 252 | if let Ok(mut data) = data.lock() { 253 | (*data).1 += 1; 254 | } 255 | std::thread::sleep(std::time::Duration::from_millis(1000)); 256 | } 257 | }); 258 | 259 | open_window( 260 | "HexoTK BlockCode Test", 261 | WINDOW_W, 262 | WINDOW_H, 263 | None, 264 | Box::new(|| { 265 | let mut style = Style::new(); 266 | style.font_size = 20.0; 267 | 268 | let style_ref = Rc::new(style.clone()); 269 | 270 | let lang = make_block_lang(); 271 | 272 | let block_fun = Arc::new(Mutex::new(BlockFun::new(lang))); 273 | { 274 | let mut block_fun_acc = block_fun.lock().unwrap(); 275 | block_fun_acc.instanciate_at(0, 3, 3, "phse", None).unwrap(); 276 | } 277 | 278 | let s = style_ref.with_style_clone(|style| { 279 | style.bg_color = hexotk::style::UI_ACCENT_BG1_CLR; 280 | style.border_style = BorderStyle::Rect; 281 | style.pad_top = 0.0; 282 | style.pad_left = 0.0; 283 | style.pad_right = 0.0; 284 | style.pad_bottom = 0.0; 285 | style.border = 2.0; 286 | style.shadow_offs = (0.0, 0.0); 287 | style.border_color = hexotk::style::UI_SELECT_CLR; 288 | style.font_size = 10.0; 289 | style.ext = StyleExt::BlockCode { 290 | with_markers: true, 291 | grid_marker_color: hexotk::style::UI_ACCENT_DARK_CLR, 292 | block_bg_hover_color: hexotk::style::UI_ACCENT_CLR, 293 | block_bg_color: hexotk::style::UI_ACCENT_BG2_CLR, 294 | port_select_color: hexotk::style::UI_SELECT_CLR, 295 | }; 296 | }); 297 | 298 | let root = Widget::new(style_ref.clone()); 299 | root.set_ctrl(Control::Rect); 300 | root.enable_cache(); 301 | root.change_layout(|l| l.layout_type = Some(LayoutType::Column)); 302 | 303 | let blockcode = Widget::new(s.clone()); 304 | blockcode 305 | .set_ctrl(Control::BlockCode { code: Box::new(BlockCode::new(block_fun.clone())) }); 306 | 307 | { 308 | let mut block_fun_acc = block_fun.lock().unwrap(); 309 | block_fun_acc.instanciate_at(0, 5, 5, "phse", None).unwrap(); 310 | } 311 | 312 | let code = block_fun.clone(); 313 | blockcode.reg("click", move |_ctx, _wid, ev| { 314 | if let EvPayload::BlockPos { button, at, .. } = ev.data { 315 | if let BlockPos::Block { row, col, .. } = at { 316 | let (id, x, y) = at.pos(); 317 | 318 | let mut code = code.lock().unwrap(); 319 | 320 | if button == MButton::Right { 321 | println!("PORT CLICK {:?}", at); 322 | code.shift_port(id, x, y, row, col == 1); 323 | } else { 324 | if col == 1 { 325 | let _ = code.split_block_chain_after(id, x, y, Some("->")); 326 | } else { 327 | let _ = code.split_block_chain_after(id, x - 1, y, None); 328 | } 329 | } 330 | 331 | let tree = code.generate_tree::("zero").unwrap(); 332 | println!("{}", tree.walk_dump("", "", 0)); 333 | 334 | code.recalculate_area_sizes(); 335 | } else { 336 | println!("CLICK POPUP {:?}", at); 337 | // state.insert_event( 338 | // Event::new(PopupEvent::OpenAtCursor) 339 | // .target(pop) 340 | // .origin(Entity::root())); 341 | } 342 | // (*on_change)(state, entity, code.clone()); 343 | } 344 | println!("CLICK: {:?}", ev); 345 | }); 346 | 347 | let code = block_fun.clone(); 348 | blockcode.reg("drag", move |_ctx, _wid, ev| { 349 | if let EvPayload::BlockPos { at, to: Some(to), button } = ev.data { 350 | println!("CLICK: {:?}", ev); 351 | let (id, x, y) = at.pos(); 352 | let (id2, x2, y2) = to.pos(); 353 | 354 | println!("P1={:?} P2={:?}", at, to); 355 | let mut code = code.lock().unwrap(); 356 | 357 | if let BlockPos::Cell { .. } = at { 358 | if let BlockPos::Block { .. } = to { 359 | let _ = code.clone_block_from_to(id2, x2, y2, id, x, y); 360 | code.recalculate_area_sizes(); 361 | 362 | // (*ouagen_change)(state, entity, code.clone()); 363 | } 364 | } else { 365 | if button == MButton::Right { 366 | let _ = code.move_block_from_to(id, x, y, id2, x2, y2); 367 | } else { 368 | if at.pos() == to.pos() { 369 | let _ = code.remove_at(id, x, y); 370 | } else { 371 | let _ = code.move_block_chain_from_to(id, x, y, id2, x2, y2); 372 | } 373 | } 374 | 375 | code.recalculate_area_sizes(); 376 | 377 | // (*on_change)(state, entity, code.clone()); 378 | } 379 | } 380 | }); 381 | 382 | root.add(blockcode); 383 | 384 | let mut ui = Box::new(UI::new(Rc::new(RefCell::new(1)))); 385 | 386 | ui.add_layer_root(root); 387 | 388 | ui 389 | }), 390 | ); 391 | } 392 | -------------------------------------------------------------------------------- /examples/hex_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeirdConstructor/HexoTK/e949aabda88f009f0d6b446d057f9aff4d4a19be/examples/hex_test.png -------------------------------------------------------------------------------- /examples/list_test.rs: -------------------------------------------------------------------------------- 1 | use hexotk::*; 2 | use std::cell::RefCell; 3 | use std::rc::Rc; 4 | use std::sync::{Arc, Mutex}; 5 | 6 | const WINDOW_W: i32 = 1020; 7 | const WINDOW_H: i32 = 720; 8 | 9 | fn main() { 10 | open_window( 11 | "HexoTK List Test", 12 | WINDOW_W, 13 | WINDOW_H, 14 | None, 15 | Box::new(|| { 16 | let mut style = Style::new(); 17 | style.font_size = 20.0; 18 | 19 | let style_ref = Rc::new(style.clone()); 20 | 21 | let s = style_ref.with_style_clone(|style| { 22 | style.bg_color = hexotk::style::UI_ACCENT_BG1_CLR; 23 | style.border_style = BorderStyle::Rect; 24 | style.pad_top = 0.0; 25 | style.pad_left = 0.0; 26 | style.pad_right = 0.0; 27 | style.pad_bottom = 0.0; 28 | style.border = 4.0; 29 | style.shadow_offs = (0.0, 0.0); 30 | style.border_color = hexotk::style::UI_SELECT_CLR; 31 | }); 32 | let root = Widget::new(s.clone()); 33 | root.set_ctrl(Control::Rect); 34 | root.enable_cache(); 35 | root.change_layout(|l| l.layout_type = Some(LayoutType::Column)); 36 | 37 | let list_data = Rc::new(RefCell::new(ListData::new())); 38 | list_data.borrow_mut().push("Test123".to_string()); 39 | list_data.borrow_mut().push("fiefi oewf eowijfewo ifjweo jwefoi jweofiew".to_string()); 40 | list_data.borrow_mut().push("Oooofeofewofe wf ewf owef ewo".to_string()); 41 | for i in 0..100 { 42 | list_data.borrow_mut().push(format!("Item {}", i)); 43 | } 44 | 45 | let list = Widget::new(s.with_style_clone(|s| { 46 | s.bg_color = hexotk::style::UI_LBL_BG_CLR; 47 | s.font_size = 18.0; 48 | s.border_color = (0.0, 1.0, 0.0); 49 | s.pad_item = 5.0; 50 | })); 51 | list.set_ctrl(Control::List { list: Box::new(List::new(list_data.clone(), ListScrollMode::Detached)) }); 52 | list.enable_cache(); 53 | list.change_layout(|l| { 54 | l.left = Some(Units::Pixels(30.0)); 55 | l.top = Some(Units::Pixels(50.0)); 56 | l.width = Some(Units::Pixels(200.0)); 57 | }); 58 | 59 | root.add(list); 60 | 61 | let mut ui = Box::new(UI::new(Rc::new(RefCell::new(1)))); 62 | 63 | ui.add_layer_root(root); 64 | 65 | ui 66 | }), 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /res/dejavu-fonts-ttf-2.37.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeirdConstructor/HexoTK/e949aabda88f009f0d6b446d057f9aff4d4a19be/res/dejavu-fonts-ttf-2.37.zip -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "Max" 2 | use_field_init_shorthand = true 3 | -------------------------------------------------------------------------------- /src/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeirdConstructor/HexoTK/e949aabda88f009f0d6b446d057f9aff4d4a19be/src/font.ttf -------------------------------------------------------------------------------- /src/font_mono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WeirdConstructor/HexoTK/e949aabda88f009f0d6b446d057f9aff4d4a19be/src/font_mono.ttf -------------------------------------------------------------------------------- /src/layout.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Weird Constructor 2 | // This file is a part of HexoTK. Released under GPL-3.0-or-later. 3 | // See README.md and COPYING for details. 4 | 5 | use crate::widget_store::WidgetStore; 6 | use crate::Rect; 7 | 8 | use std::cell::RefCell; 9 | use std::collections::HashMap; 10 | use std::rc::Rc; 11 | 12 | use morphorm::{Cache, GeometryChanged, LayoutType, Node, PositionType, Units}; 13 | 14 | #[derive(Debug, Default, Clone, Copy)] 15 | pub struct CachedLayout { 16 | geometry_changed: GeometryChanged, 17 | 18 | width: f32, 19 | height: f32, 20 | posx: f32, 21 | posy: f32, 22 | 23 | left: f32, 24 | right: f32, 25 | top: f32, 26 | bottom: f32, 27 | 28 | new_width: f32, 29 | new_height: f32, 30 | 31 | child_width_max: f32, 32 | child_width_sum: f32, 33 | child_height_max: f32, 34 | child_height_sum: f32, 35 | grid_row_max: f32, 36 | grid_col_max: f32, 37 | 38 | horizontal_free_space: f32, 39 | vertical_free_space: f32, 40 | 41 | vertical_stretch_sum: f32, 42 | horizontal_stretch_sum: f32, 43 | 44 | stack_first_child: bool, 45 | stack_last_child: bool, 46 | } 47 | 48 | pub struct LayoutCache { 49 | layouts: HashMap, 50 | store: Rc>, 51 | } 52 | 53 | impl LayoutCache { 54 | pub fn new(store: Rc>) -> Self { 55 | Self { layouts: HashMap::new(), store } 56 | } 57 | 58 | pub fn get_widget_rect_by_id(&self, w_id: &WidgetId) -> Rect { 59 | if let Some(layout) = self.layouts.get(&w_id.unique_id()) { 60 | Rect { x: layout.posx, y: layout.posy, w: layout.width, h: layout.height } 61 | } else { 62 | Rect::from(0.0, 0.0, 0.0, 0.0) 63 | } 64 | } 65 | 66 | pub fn layout_mut R>( 67 | &mut self, 68 | w_id: &WidgetId, 69 | f: F, 70 | ) -> R { 71 | if let Some(layout) = self.layouts.get_mut(&w_id.unique_id()) { 72 | f(layout) 73 | } else { 74 | R::default() 75 | } 76 | } 77 | 78 | pub fn layout R>(&self, w_id: &WidgetId, f: F) -> R { 79 | if let Some(layout) = self.layouts.get(&w_id.unique_id()) { 80 | f(layout) 81 | } else { 82 | R::default() 83 | } 84 | } 85 | 86 | pub fn clear(&mut self) { 87 | self.layouts.clear(); 88 | self.store.borrow_mut().for_each_widget(|wid| { 89 | self.layouts.insert(wid.unique_id(), CachedLayout::default()); 90 | }); 91 | } 92 | } 93 | 94 | impl Cache for LayoutCache { 95 | type Item = WidgetId; 96 | 97 | fn geometry_changed(&self, node: Self::Item) -> GeometryChanged { 98 | self.layout(&node, |l| l.geometry_changed) 99 | } 100 | 101 | fn visible(&self, node: Self::Item) -> bool { 102 | self.store 103 | .borrow() 104 | .get(node.unique_id()) 105 | .map(|w| w.with_layout(|l| l.visible)) 106 | .unwrap_or(false) 107 | } 108 | 109 | fn width(&self, node: Self::Item) -> f32 { 110 | self.layout(&node, |l| l.width) 111 | } 112 | 113 | fn height(&self, node: Self::Item) -> f32 { 114 | self.layout(&node, |l| l.height) 115 | } 116 | 117 | fn posx(&self, node: Self::Item) -> f32 { 118 | self.layout(&node, |l| l.posx) 119 | } 120 | 121 | fn posy(&self, node: Self::Item) -> f32 { 122 | self.layout(&node, |l| l.posy) 123 | } 124 | 125 | fn left(&self, node: Self::Item) -> f32 { 126 | self.layout(&node, |l| l.left) 127 | } 128 | fn right(&self, node: Self::Item) -> f32 { 129 | self.layout(&node, |l| l.right) 130 | } 131 | fn top(&self, node: Self::Item) -> f32 { 132 | self.layout(&node, |l| l.top) 133 | } 134 | fn bottom(&self, node: Self::Item) -> f32 { 135 | self.layout(&node, |l| l.bottom) 136 | } 137 | 138 | fn new_width(&self, node: Self::Item) -> f32 { 139 | self.layout(&node, |l| l.new_width) 140 | } 141 | fn new_height(&self, node: Self::Item) -> f32 { 142 | self.layout(&node, |l| l.new_height) 143 | } 144 | 145 | fn child_width_max(&self, node: Self::Item) -> f32 { 146 | self.layout(&node, |l| l.child_width_max) 147 | } 148 | 149 | fn child_width_sum(&self, node: Self::Item) -> f32 { 150 | self.layout(&node, |l| l.child_width_sum) 151 | } 152 | 153 | fn child_height_max(&self, node: Self::Item) -> f32 { 154 | self.layout(&node, |l| l.child_height_max) 155 | } 156 | 157 | fn child_height_sum(&self, node: Self::Item) -> f32 { 158 | self.layout(&node, |l| l.child_height_sum) 159 | } 160 | 161 | fn grid_row_max(&self, node: Self::Item) -> f32 { 162 | self.layout(&node, |l| l.grid_row_max) 163 | } 164 | 165 | fn grid_col_max(&self, node: Self::Item) -> f32 { 166 | self.layout(&node, |l| l.grid_col_max) 167 | } 168 | 169 | fn set_grid_col_max(&mut self, node: Self::Item, value: f32) { 170 | self.layout_mut(&node, |l| l.grid_col_max = value) 171 | } 172 | 173 | fn set_grid_row_max(&mut self, node: Self::Item, value: f32) { 174 | self.layout_mut(&node, |l| l.grid_row_max = value) 175 | } 176 | 177 | fn set_visible(&mut self, _node: Self::Item, _value: bool) { 178 | // nop 179 | } 180 | 181 | fn set_geo_changed(&mut self, node: Self::Item, flag: GeometryChanged, value: bool) { 182 | self.layout_mut(&node, |l| l.geometry_changed.set(flag, value)) 183 | } 184 | 185 | fn set_child_width_sum(&mut self, node: Self::Item, value: f32) { 186 | self.layout_mut(&node, |l| l.child_width_sum = value) 187 | } 188 | fn set_child_height_sum(&mut self, node: Self::Item, value: f32) { 189 | self.layout_mut(&node, |l| l.child_height_sum = value) 190 | } 191 | fn set_child_width_max(&mut self, node: Self::Item, value: f32) { 192 | self.layout_mut(&node, |l| l.child_width_max = value) 193 | } 194 | fn set_child_height_max(&mut self, node: Self::Item, value: f32) { 195 | self.layout_mut(&node, |l| l.child_height_max = value) 196 | } 197 | 198 | fn horizontal_free_space(&self, node: Self::Item) -> f32 { 199 | self.layout(&node, |l| l.horizontal_free_space) 200 | } 201 | fn set_horizontal_free_space(&mut self, node: Self::Item, value: f32) { 202 | self.layout_mut(&node, |l| l.horizontal_free_space = value) 203 | } 204 | fn vertical_free_space(&self, node: Self::Item) -> f32 { 205 | self.layout(&node, |l| l.vertical_free_space) 206 | } 207 | fn set_vertical_free_space(&mut self, node: Self::Item, value: f32) { 208 | self.layout_mut(&node, |l| l.vertical_free_space = value) 209 | } 210 | 211 | fn horizontal_stretch_sum(&self, node: Self::Item) -> f32 { 212 | self.layout(&node, |l| l.horizontal_stretch_sum) 213 | } 214 | fn set_horizontal_stretch_sum(&mut self, node: Self::Item, value: f32) { 215 | self.layout_mut(&node, |l| l.horizontal_stretch_sum = value) 216 | } 217 | fn vertical_stretch_sum(&self, node: Self::Item) -> f32 { 218 | self.layout(&node, |l| l.vertical_stretch_sum) 219 | } 220 | fn set_vertical_stretch_sum(&mut self, node: Self::Item, value: f32) { 221 | self.layout_mut(&node, |l| l.vertical_stretch_sum = value) 222 | } 223 | 224 | fn set_width(&mut self, node: Self::Item, value: f32) { 225 | self.layout_mut(&node, |l| l.width = value) 226 | } 227 | fn set_height(&mut self, node: Self::Item, value: f32) { 228 | self.layout_mut(&node, |l| l.height = value) 229 | } 230 | fn set_posx(&mut self, node: Self::Item, value: f32) { 231 | self.layout_mut(&node, |l| l.posx = value) 232 | } 233 | fn set_posy(&mut self, node: Self::Item, value: f32) { 234 | self.layout_mut(&node, |l| l.posy = value) 235 | } 236 | 237 | fn set_left(&mut self, node: Self::Item, value: f32) { 238 | self.layout_mut(&node, |l| l.left = value) 239 | } 240 | fn set_right(&mut self, node: Self::Item, value: f32) { 241 | self.layout_mut(&node, |l| l.right = value) 242 | } 243 | fn set_top(&mut self, node: Self::Item, value: f32) { 244 | self.layout_mut(&node, |l| l.top = value) 245 | } 246 | fn set_bottom(&mut self, node: Self::Item, value: f32) { 247 | self.layout_mut(&node, |l| l.bottom = value) 248 | } 249 | 250 | fn set_new_width(&mut self, node: Self::Item, value: f32) { 251 | self.layout_mut(&node, |l| l.new_width = value) 252 | } 253 | fn set_new_height(&mut self, node: Self::Item, value: f32) { 254 | self.layout_mut(&node, |l| l.new_height = value) 255 | } 256 | 257 | fn stack_first_child(&self, node: Self::Item) -> bool { 258 | self.layout(&node, |l| l.stack_first_child) 259 | } 260 | fn set_stack_first_child(&mut self, node: Self::Item, value: bool) { 261 | self.layout_mut(&node, |l| l.stack_first_child = value) 262 | } 263 | fn stack_last_child(&self, node: Self::Item) -> bool { 264 | self.layout(&node, |l| l.stack_last_child) 265 | } 266 | fn set_stack_last_child(&mut self, node: Self::Item, value: bool) { 267 | self.layout_mut(&node, |l| l.stack_last_child = value) 268 | } 269 | } 270 | 271 | #[derive(Debug, Clone, Copy)] 272 | pub struct WidgetId { 273 | unique_id: usize, 274 | } 275 | 276 | impl WidgetId { 277 | pub fn from(unique_id: usize) -> Self { 278 | Self { unique_id } 279 | } 280 | 281 | pub fn unique_id(&self) -> usize { 282 | self.unique_id 283 | } 284 | } 285 | 286 | #[derive(Clone)] 287 | pub struct LayoutTree { 288 | pub dpi_factor: f32, 289 | pub store: Rc>, 290 | } 291 | 292 | macro_rules! get_size { 293 | ($self: ident, $tree: ident, $field: ident) => { 294 | match $tree.store.borrow().with_layout($self, |l| l.$field) { 295 | Some(Units::Pixels(px)) => Some(Units::Pixels(px * $tree.dpi_factor)), 296 | u => u, 297 | } 298 | }; 299 | } 300 | 301 | impl Node<'_> for WidgetId { 302 | type Data = LayoutTree; 303 | 304 | fn layout_type(&self, tree: &'_ Self::Data) -> Option { 305 | tree.store.borrow().with_layout(self, |l| l.layout_type) 306 | } 307 | 308 | fn position_type(&self, tree: &'_ Self::Data) -> Option { 309 | tree.store.borrow().with_layout(self, |l| l.position_type) 310 | } 311 | 312 | fn width(&self, tree: &'_ Self::Data) -> Option { 313 | get_size!(self, tree, width) 314 | } 315 | 316 | fn height(&self, tree: &'_ Self::Data) -> Option { 317 | get_size!(self, tree, height) 318 | } 319 | 320 | fn min_width(&self, tree: &'_ Self::Data) -> Option { 321 | get_size!(self, tree, min_width) 322 | } 323 | 324 | fn min_height(&self, tree: &'_ Self::Data) -> Option { 325 | get_size!(self, tree, min_height) 326 | } 327 | 328 | fn max_width(&self, tree: &'_ Self::Data) -> Option { 329 | get_size!(self, tree, max_width) 330 | } 331 | 332 | fn max_height(&self, tree: &'_ Self::Data) -> Option { 333 | get_size!(self, tree, max_height) 334 | } 335 | 336 | fn left(&self, tree: &'_ Self::Data) -> Option { 337 | get_size!(self, tree, left) 338 | } 339 | 340 | fn right(&self, tree: &'_ Self::Data) -> Option { 341 | get_size!(self, tree, right) 342 | } 343 | 344 | fn top(&self, tree: &'_ Self::Data) -> Option { 345 | get_size!(self, tree, top) 346 | } 347 | 348 | fn bottom(&self, tree: &'_ Self::Data) -> Option { 349 | get_size!(self, tree, bottom) 350 | } 351 | 352 | fn min_left(&self, tree: &'_ Self::Data) -> Option { 353 | get_size!(self, tree, min_left) 354 | } 355 | 356 | fn max_left(&self, tree: &'_ Self::Data) -> Option { 357 | get_size!(self, tree, max_left) 358 | } 359 | 360 | fn min_right(&self, tree: &'_ Self::Data) -> Option { 361 | get_size!(self, tree, min_right) 362 | } 363 | 364 | fn max_right(&self, tree: &'_ Self::Data) -> Option { 365 | get_size!(self, tree, max_right) 366 | } 367 | 368 | fn min_top(&self, tree: &'_ Self::Data) -> Option { 369 | get_size!(self, tree, min_top) 370 | } 371 | 372 | fn max_top(&self, tree: &'_ Self::Data) -> Option { 373 | get_size!(self, tree, max_top) 374 | } 375 | 376 | fn min_bottom(&self, tree: &'_ Self::Data) -> Option { 377 | get_size!(self, tree, min_bottom) 378 | } 379 | 380 | fn max_bottom(&self, tree: &'_ Self::Data) -> Option { 381 | get_size!(self, tree, max_bottom) 382 | } 383 | 384 | fn child_left(&self, tree: &'_ Self::Data) -> Option { 385 | get_size!(self, tree, child_left) 386 | } 387 | 388 | fn child_right(&self, tree: &'_ Self::Data) -> Option { 389 | get_size!(self, tree, child_right) 390 | } 391 | 392 | fn child_top(&self, tree: &'_ Self::Data) -> Option { 393 | get_size!(self, tree, child_top) 394 | } 395 | 396 | fn child_bottom(&self, tree: &'_ Self::Data) -> Option { 397 | get_size!(self, tree, child_bottom) 398 | } 399 | 400 | fn row_between(&self, tree: &'_ Self::Data) -> Option { 401 | get_size!(self, tree, row_between) 402 | } 403 | 404 | fn col_between(&self, tree: &'_ Self::Data) -> Option { 405 | get_size!(self, tree, col_between) 406 | } 407 | 408 | fn grid_rows(&self, tree: &'_ Self::Data) -> Option> { 409 | tree.store.borrow().with_layout(self, |l| l.grid_rows.clone()) 410 | } 411 | 412 | fn grid_cols(&self, tree: &'_ Self::Data) -> Option> { 413 | tree.store.borrow().with_layout(self, |l| l.grid_cols.clone()) 414 | } 415 | 416 | fn row_index(&self, tree: &'_ Self::Data) -> Option { 417 | tree.store.borrow().with_layout(self, |l| l.row_index) 418 | } 419 | 420 | fn col_index(&self, tree: &'_ Self::Data) -> Option { 421 | tree.store.borrow().with_layout(self, |l| l.col_index) 422 | } 423 | fn row_span(&self, tree: &'_ Self::Data) -> Option { 424 | tree.store.borrow().with_layout(self, |l| l.row_span) 425 | } 426 | fn col_span(&self, tree: &'_ Self::Data) -> Option { 427 | tree.store.borrow().with_layout(self, |l| l.col_span) 428 | } 429 | fn border_left(&self, tree: &'_ Self::Data) -> Option { 430 | let w = tree.store.borrow().get(self.unique_id)?; 431 | let style = w.style(); 432 | if w.has_default_style() { 433 | Some(Units::Pixels(tree.dpi_factor * (style.border + style.pad_left))) 434 | } else { 435 | Some(Units::Pixels(tree.dpi_factor * style.pad_left)) 436 | } 437 | } 438 | fn border_right(&self, tree: &'_ Self::Data) -> Option { 439 | let w = tree.store.borrow().get(self.unique_id)?; 440 | let style = w.style(); 441 | if w.has_default_style() { 442 | Some(Units::Pixels(tree.dpi_factor * (style.border + style.pad_right))) 443 | } else { 444 | Some(Units::Pixels(tree.dpi_factor * style.pad_right)) 445 | } 446 | } 447 | fn border_top(&self, tree: &'_ Self::Data) -> Option { 448 | let w = tree.store.borrow().get(self.unique_id)?; 449 | let style = w.style(); 450 | if w.has_default_style() { 451 | Some(Units::Pixels(tree.dpi_factor * (style.border + style.pad_top))) 452 | } else { 453 | Some(Units::Pixels(tree.dpi_factor * style.pad_top)) 454 | } 455 | } 456 | fn border_bottom(&self, tree: &'_ Self::Data) -> Option { 457 | let w = tree.store.borrow().get(self.unique_id)?; 458 | let style = w.style(); 459 | if w.has_default_style() { 460 | Some(Units::Pixels(tree.dpi_factor * (style.border + style.pad_bottom))) 461 | } else { 462 | Some(Units::Pixels(tree.dpi_factor * style.pad_bottom)) 463 | } 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /src/markdown.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Weird Constructor 2 | // This file is a part of HexoTK. Released under GPL-3.0-or-later. 3 | // See README.md and COPYING for details. 4 | 5 | use pulldown_cmark::{Event, HeadingLevel, CodeBlockKind, Options, Parser, Tag}; 6 | 7 | pub struct MarkdownWichtextGenerator { 8 | header_color_font_size: Vec<(u8, u8)>, 9 | block_width: u16, 10 | text_lines: Vec, 11 | } 12 | 13 | fn escape(s: &str) -> String { 14 | s.replace("[", "[[").replace("]", "]]") 15 | } 16 | 17 | const CODE_COLOR_IDX: u8 = 15; 18 | const LINK_COLOR_IDX: u8 = 8; 19 | const STRONG_COLOR_IDX: u8 = 4; 20 | const EMPHASIS_COLOR_IDX: u8 = 2; 21 | const STRIKE_COLOR_IDX: u8 = 11; 22 | const LIST_MARK_COLOR_IDX: u8 = 17; 23 | 24 | struct Style { 25 | add_fmt: Option, 26 | color: Option, 27 | size: Option, 28 | raw: bool, 29 | code: bool, 30 | } 31 | 32 | impl Style { 33 | pub fn new() -> Self { 34 | Self { add_fmt: None, color: None, size: None, raw: false, code: false } 35 | } 36 | 37 | pub fn with_list_mark(&self) -> Self { 38 | Self { 39 | add_fmt: self.add_fmt.clone(), 40 | color: Some(LIST_MARK_COLOR_IDX), 41 | size: self.size, 42 | raw: self.raw, 43 | code: false, 44 | } 45 | } 46 | 47 | pub fn with_strong(&self) -> Self { 48 | Self { 49 | add_fmt: self.add_fmt.clone(), 50 | color: Some(STRONG_COLOR_IDX), 51 | size: self.size, 52 | raw: self.raw, 53 | code: false, 54 | } 55 | } 56 | 57 | pub fn with_emphasis(&self) -> Self { 58 | Self { 59 | add_fmt: self.add_fmt.clone(), 60 | color: Some(EMPHASIS_COLOR_IDX), 61 | size: self.size, 62 | raw: self.raw, 63 | code: false, 64 | } 65 | } 66 | 67 | pub fn with_strike(&self) -> Self { 68 | Self { 69 | add_fmt: self.add_fmt.clone(), 70 | color: Some(STRIKE_COLOR_IDX), 71 | size: self.size, 72 | raw: self.raw, 73 | code: false, 74 | } 75 | } 76 | 77 | pub fn with_raw_code(&self) -> Self { 78 | Self { 79 | add_fmt: None, 80 | color: None, 81 | size: None, 82 | raw: true, 83 | code: true, 84 | } 85 | } 86 | 87 | pub fn with_code(&self) -> Self { 88 | Self { 89 | add_fmt: self.add_fmt.clone(), 90 | color: Some(CODE_COLOR_IDX), 91 | size: self.size, 92 | raw: self.raw, 93 | code: true, 94 | } 95 | } 96 | 97 | pub fn with_link(&self, lref: &str) -> Self { 98 | if lref.starts_with("$") { 99 | Self { 100 | add_fmt: Some(lref.trim_start_matches('$').to_string()), 101 | color: None, 102 | size: None, 103 | raw: false, 104 | code: false, 105 | } 106 | 107 | } else { 108 | let mut add_fmt = 109 | if let Some(add_fmt) = &self.add_fmt { add_fmt.to_string() } else { String::new() }; 110 | add_fmt += "a"; 111 | Self { 112 | add_fmt: Some(add_fmt), 113 | color: Some(LINK_COLOR_IDX), 114 | size: self.size, 115 | raw: lref == "$", 116 | code: false, 117 | } 118 | } 119 | } 120 | 121 | pub fn with_heading_level(&self, hl: HeadingLevel, heading_styles: &[(u8, u8)]) -> Self { 122 | let index = match hl { 123 | HeadingLevel::H1 => 0, 124 | HeadingLevel::H2 => 1, 125 | HeadingLevel::H3 => 2, 126 | HeadingLevel::H4 => 3, 127 | HeadingLevel::H5 => 4, 128 | HeadingLevel::H6 => 5, 129 | }; 130 | 131 | let size_idx = index.min(heading_styles.len() - 1); 132 | let (color, size) = heading_styles[size_idx]; 133 | 134 | Self { 135 | add_fmt: self.add_fmt.clone(), 136 | color: Some(color), 137 | size: Some(size), 138 | raw: self.raw, 139 | code: false, 140 | } 141 | } 142 | 143 | pub fn in_code(&self) -> bool { 144 | self.code 145 | } 146 | 147 | pub fn fmt_word(&self, word: &str) -> String { 148 | if self.raw { 149 | return word.to_string(); 150 | } 151 | if word.is_empty() { 152 | return String::new(); 153 | } 154 | 155 | let mut fmt = String::new(); 156 | 157 | if let Some(size) = self.size { 158 | fmt += &format!("f{}", size); 159 | } 160 | if let Some(color) = self.color { 161 | fmt += &format!("c{}", color); 162 | } 163 | if let Some(add_fmt) = &self.add_fmt { 164 | fmt += add_fmt; 165 | } 166 | 167 | let word = escape(word); 168 | if fmt.len() > 0 { 169 | format!("[{}:{}]", fmt, word) 170 | } else { 171 | word 172 | } 173 | } 174 | } 175 | 176 | fn indent_str(indent: u16) -> String { 177 | let mut indent_s = String::new(); 178 | for _ in 0..indent { 179 | indent_s += " "; 180 | } 181 | indent_s 182 | } 183 | 184 | struct BlockLayout { 185 | indent: u16, 186 | indent_stack: Vec, 187 | width: usize, 188 | cur_line: String, 189 | cur_line_w: usize, 190 | } 191 | 192 | impl BlockLayout { 193 | pub fn new(width: usize) -> Self { 194 | Self { 195 | indent: 0, 196 | indent_stack: vec![], 197 | width, 198 | cur_line: String::new(), 199 | cur_line_w: 0, 200 | } 201 | } 202 | 203 | pub fn push_indent(&mut self, inc: u16) { 204 | self.indent_stack.push(self.indent); 205 | self.indent += inc; 206 | } 207 | 208 | pub fn pop_indent(&mut self) { 209 | self.indent = self.indent_stack.pop().unwrap_or(0); 210 | } 211 | 212 | pub fn indent(&self) -> u16 { self.indent } 213 | 214 | pub fn force_space(&mut self) { 215 | self.cur_line += " "; 216 | self.cur_line_w += 1; 217 | } 218 | 219 | pub fn ensure_space(&mut self) { 220 | if self.cur_line_w > 0 { 221 | if self.cur_line.chars().last().unwrap_or('_') != ' ' { 222 | self.cur_line += " "; 223 | self.cur_line_w += 1; 224 | } 225 | } 226 | } 227 | 228 | pub fn add_words_from_string(&mut self, s: &str, style: &Style, out_lines: &mut Vec) { 229 | let indent_s = indent_str(self.indent); 230 | 231 | if self.cur_line.is_empty() { 232 | self.cur_line = indent_s.clone(); 233 | self.cur_line_w = indent_s.len(); 234 | } 235 | 236 | if style.in_code() { 237 | self.cur_line += &style.fmt_word(s.trim_end()); 238 | self.cur_line_w = self.cur_line.len(); 239 | return; 240 | } 241 | 242 | let words: Vec<&str> = s.split(" ").collect(); 243 | 244 | let mut started_block = true; 245 | 246 | for word in words.iter() { 247 | let word = word.trim(); 248 | 249 | if self.cur_line_w > 0 250 | && (self.cur_line_w + word.len()) > self.width.into() 251 | { 252 | out_lines.push(self.cur_line.clone()); 253 | self.cur_line = indent_s.clone(); 254 | self.cur_line_w = indent_s.len(); 255 | } 256 | 257 | if !started_block && self.cur_line.find(|c| !char::is_whitespace(c)).is_some() { 258 | self.cur_line += " "; 259 | self.cur_line_w += 1; 260 | } 261 | 262 | self.cur_line += &style.fmt_word(word); 263 | self.cur_line_w += word.len(); 264 | 265 | started_block = false; 266 | } 267 | } 268 | 269 | pub fn flush(&mut self, out_lines: &mut Vec) { 270 | if self.cur_line_w > 0 { 271 | out_lines.push(self.cur_line.clone()); 272 | self.cur_line = String::new(); 273 | self.cur_line_w = 0; 274 | } 275 | } 276 | } 277 | 278 | impl MarkdownWichtextGenerator { 279 | pub fn new(bw: u16) -> Self { 280 | Self { 281 | header_color_font_size: vec![(15, 22), (11, 21), (7, 20), (17, 19)], 282 | block_width: bw, 283 | text_lines: vec![], 284 | } 285 | } 286 | 287 | fn ensure_empty_line(&mut self) { 288 | let prev_empty = if let Some(l) = self.text_lines.last() { l.is_empty() } else { true }; 289 | if !prev_empty { 290 | self.text_lines.push(String::new()); 291 | } 292 | } 293 | 294 | pub fn parse(&mut self, txt: &str) { 295 | let mut options = Options::empty(); 296 | options.insert(Options::ENABLE_STRIKETHROUGH); 297 | let parser = Parser::new_ext(txt, options); 298 | 299 | let mut layout = BlockLayout::new(self.block_width.into()); 300 | 301 | let mut style_stack = vec![Style::new()]; 302 | 303 | let mut list_stack = vec![]; 304 | let mut current_list_index = None; 305 | 306 | for ev in parser { 307 | //d// println!("EVENT: {:?}", ev); 308 | 309 | match ev { 310 | Event::Rule => { 311 | let indent_s = indent_str(layout.indent); 312 | let mut dashes = String::from("[c11:"); 313 | for _ in 0..self.block_width { 314 | dashes += "-"; 315 | } 316 | dashes += "]"; 317 | self.ensure_empty_line(); 318 | self.text_lines.push(indent_s + &dashes); 319 | } 320 | Event::Start(tag) => match tag { 321 | Tag::List(start) => { 322 | list_stack.push(current_list_index); 323 | current_list_index = start; 324 | layout.flush(&mut self.text_lines); 325 | } 326 | Tag::Item => { 327 | layout.flush(&mut self.text_lines); 328 | let item = if let Some(index) = &mut current_list_index { 329 | *index += 1; 330 | format!("{}", *index - 1) 331 | } else { 332 | "*".to_string() 333 | }; 334 | layout.add_words_from_string( 335 | &item, 336 | &style_stack.last().unwrap().with_list_mark(), 337 | &mut self.text_lines, 338 | ); 339 | layout.force_space(); 340 | layout.push_indent(2); 341 | } 342 | Tag::Image(_, lref, _) => { 343 | let v: Vec<&str> = lref.split("?").collect(); 344 | self.text_lines.push( 345 | format!( 346 | "[h{}I{}:]", 347 | v.get(1).unwrap_or(&"300"), 348 | v.get(0).unwrap_or(&"broken.png"))); 349 | } 350 | Tag::Link(_, lref, _) => { 351 | style_stack.push(style_stack.last().unwrap().with_link(&lref)); 352 | } 353 | Tag::CodeBlock(code_type) => { 354 | layout.flush(&mut self.text_lines); 355 | self.ensure_empty_line(); 356 | 357 | match code_type { 358 | CodeBlockKind::Fenced(lang) => { 359 | if &*lang == "wichtext" { 360 | style_stack.push(style_stack.last().unwrap().with_raw_code()); 361 | } else { 362 | style_stack.push(style_stack.last().unwrap().with_code()); 363 | } 364 | layout.push_indent(0); 365 | }, 366 | CodeBlockKind::Indented => { 367 | style_stack.push(style_stack.last().unwrap().with_code()); 368 | layout.push_indent(4); 369 | }, 370 | } 371 | } 372 | Tag::Heading(hl, _, _) => { 373 | layout.flush(&mut self.text_lines); 374 | self.ensure_empty_line(); 375 | 376 | style_stack.push( 377 | style_stack 378 | .last() 379 | .unwrap() 380 | .with_heading_level(hl, &self.header_color_font_size), 381 | ); 382 | } 383 | Tag::Strong => { 384 | style_stack.push(style_stack.last().unwrap().with_strong()); 385 | } 386 | Tag::Emphasis => { 387 | style_stack.push(style_stack.last().unwrap().with_emphasis()); 388 | } 389 | Tag::Strikethrough => { 390 | style_stack.push(style_stack.last().unwrap().with_strike()); 391 | } 392 | _ => {} 393 | }, 394 | Event::End(tag) => match tag { 395 | Tag::CodeBlock(_) => { 396 | style_stack.pop(); 397 | layout.flush(&mut self.text_lines); 398 | layout.pop_indent(); 399 | self.ensure_empty_line(); 400 | } 401 | Tag::List(_) => { 402 | current_list_index = list_stack.pop().flatten(); 403 | if layout.indent() == 0 { 404 | self.ensure_empty_line(); 405 | } 406 | } 407 | Tag::Image(_, _, _) => {} 408 | Tag::Link(_, _, _) => { 409 | style_stack.pop(); 410 | } 411 | Tag::Item => { 412 | layout.flush(&mut self.text_lines); 413 | layout.pop_indent(); 414 | } 415 | Tag::Heading(_, _, _) => { 416 | style_stack.pop(); 417 | layout.flush(&mut self.text_lines); 418 | self.ensure_empty_line(); 419 | } 420 | Tag::Paragraph => { 421 | layout.flush(&mut self.text_lines); 422 | self.ensure_empty_line(); 423 | } 424 | Tag::Strong => { 425 | style_stack.pop(); 426 | } 427 | Tag::Emphasis => { 428 | style_stack.pop(); 429 | } 430 | Tag::Strikethrough => { 431 | style_stack.pop(); 432 | } 433 | _ => {} 434 | }, 435 | Event::Code(s) => { 436 | style_stack.push(style_stack.last().unwrap().with_code()); 437 | layout.add_words_from_string( 438 | &s, 439 | style_stack.last().unwrap(), 440 | &mut self.text_lines, 441 | ); 442 | style_stack.pop(); 443 | } 444 | Event::Text(s) => { 445 | layout.add_words_from_string( 446 | &s, 447 | style_stack.last().unwrap(), 448 | &mut self.text_lines, 449 | ); 450 | if style_stack.last().unwrap().in_code() { 451 | layout.flush(&mut self.text_lines); 452 | } 453 | } 454 | Event::HardBreak => { 455 | layout.flush(&mut self.text_lines); 456 | } 457 | Event::SoftBreak => { 458 | layout.ensure_space(); 459 | } 460 | _ => {} 461 | } 462 | } 463 | } 464 | 465 | pub fn to_string(&self) -> String { 466 | self.text_lines.join("\n") 467 | } 468 | } 469 | 470 | #[cfg(test)] 471 | mod tests { 472 | use super::*; 473 | 474 | #[test] 475 | fn check_mkd2wt_1() { 476 | let mut mwg = MarkdownWichtextGenerator::new(20); 477 | let text = r#" 478 | 479 | 480 | fpoiewj fewoifj ewoifeowj 481 | f weiofj eiwoofj wejfwe 482 | feiowjfeiowfeiowfwiofew 483 | 484 | =================== 485 | 486 | feiofj wiofjwowe 487 | f weoifewj ioewj fweo feiwjfewoi 488 | 489 | ------- 490 | 491 | Test 492 | ==== 493 | 494 | ## Test 123 *foobar* __LOL__ ´323423423´ 495 | 496 | AAA `cccc` DDDD 497 | 498 | ------- 499 | 500 | BBBBB 501 | 502 | ### And here: [Foobar]() 503 | 504 | Test 123 fiuowe fieuwf hewuif hewiuf weiuf hweifu wehfi uwehf iweufh ewiuf hweiuf weiuf weiuf 505 | Test 123 fiuowe fieuwf hewuif hewiuf weiuf hweifu wehfi uwehf iweufh ewiuf hweiuf weiuf weiuf 506 | Test 123 _fiuowe fieuwf hewuif hewiuf_ weiuf hweifu wehfi uwehf iweufh ewiuf hweiuf weiuf weiuf 507 | Test 123 fiuowe fieuwf hewuif hewiuf weiuf hweifu wehfi uwehf iweufh ewiuf hweiuf weiuf weiuf 508 | Test 123 fiuowe fieuwf hewuif hewiuf weiuf hweifu wehfi uwehf iweufh ewiuf hweiuf weiuf weiuf 509 | 510 | 1. List Itmee 1 511 | 2. List Item 2 512 | 2. List Item 513 | foieuwj fewo fejwiof ewjfioe wiofj weoif iofwe 514 | foieuwj fewo fejwiof ewjfioe wiofj weoif iofwe 515 | foieuwj fewo fejwiof ewjfioe wiofj weoif iofwe 516 | foieuwj fewo fejwiof ewjfioe wiofj weoif iofwe 517 | ```text 518 | Test 123 892u 923u 2389r 2389rj 98ew 519 | Test 123 892u 923u 2389r 2389rj 98ew 520 | Test 123 892u 923u 2389r 2389rj 98ew 521 | ``` 522 | 3. Foobar lololol 523 | * Item A 524 | * Item B 525 | * Item C 526 | * Item D 527 | 528 | Intdent start: 529 | 530 | fiif ewoif ejwoifw joiwej foiwef jeowiefwoi fjiowe 531 | fiif ewoif ejwoifw joiwej foiwef jeowiefwoi fjiowe 532 | fiif ewoif ejwoifw joiwej foiwef jeowiefwoi fjiowe 533 | fiif ewoif ejwoifw joiwej foiwef jeowiefwoi fjiowe 534 | fiif ewoif ejwoifw joiwej foiwef jeowiefwoi fjiowe 535 | 536 | eindent end 537 | 538 | --------------------------------------- 539 | ``` 540 | COde blcapfcelfw 541 | feiow fewf 542 | ``` 543 | - Other A 544 | - Other B 545 | - Other C 546 | - Other D 547 | - Other E 548 | 549 | > ## Bla bla 550 | > feoiwfjew ofew 551 | > feoiwfjew ofew 552 | > feoiwfjew ofew 553 | > feoiwfjew ofew 554 | 555 | [[c15f30:Foobar] Lol]($) 556 | 557 | Image here: ![](main/bla.png) 558 | 559 | **Strong text here lo lof efew jofiewj oiewfjoi we** 560 | 561 | *Emphasis text hefew ewfhweiu fhweiu hewiuf ewiufhew* 562 | 563 | ~~Strike~~ 564 | "#; 565 | // for block in tree.iter() { 566 | // mwg.append_block(block, 0); 567 | // } 568 | mwg.parse(text); 569 | println!("RES:\n{}", mwg.to_string()); 570 | } 571 | 572 | #[test] 573 | fn check_mkd2wt_para() { 574 | let mut mwg = MarkdownWichtextGenerator::new(50); 575 | mwg.parse("A\n\nC\n"); 576 | assert_eq!(mwg.to_string(), "A\n\nC\n"); 577 | } 578 | 579 | #[test] 580 | fn check_mkd2wt_lists() { 581 | let mut mwg = MarkdownWichtextGenerator::new(50); 582 | mwg.parse("- A\n - B\n- C\n"); 583 | println!("RES:\n{}", mwg.to_string()); 584 | assert_eq!(mwg.to_string(), "[c17:*] A\n [c17:*] B\n[c17:*] C\n"); 585 | 586 | let mut mwg = MarkdownWichtextGenerator::new(50); 587 | mwg.parse("- A\n - B\n\n- C\n"); 588 | println!("RES:\n{}", mwg.to_string()); 589 | assert_eq!(mwg.to_string(), "[c17:*] A\n\n [c17:*] B\n[c17:*] C\n"); 590 | } 591 | 592 | #[test] 593 | fn check_mkd2wt_emph() { 594 | let mut mwg = MarkdownWichtextGenerator::new(50); 595 | mwg.parse("A*B*C"); 596 | println!("RES:\n{}", mwg.to_string()); 597 | assert_eq!(mwg.to_string(), "A[c2:B]C\n"); 598 | 599 | let mut mwg = MarkdownWichtextGenerator::new(50); 600 | mwg.parse("AC"); 601 | println!("RES:\n{}", mwg.to_string()); 602 | assert_eq!(mwg.to_string(), "A[c8a:B@fo.de]C\n"); 603 | 604 | let mut mwg = MarkdownWichtextGenerator::new(50); 605 | mwg.parse("A C"); 606 | println!("RES:\n{}", mwg.to_string()); 607 | assert_eq!(mwg.to_string(), "A [c8a:B@fo.de] C\n"); 608 | } 609 | 610 | #[test] 611 | fn check_mkd2wt_softbreaks() { 612 | let mut mwg = MarkdownWichtextGenerator::new(10); 613 | mwg.parse("soft breaks\nsoft\nbreaks\nsoft\nbreaks"); 614 | println!("RES:\n{}", mwg.to_string()); 615 | assert_eq!(mwg.to_string(), "soft breaks \nsoft \nbreaks \nsoft \nbreaks\n"); 616 | } 617 | 618 | #[test] 619 | fn check_mkd2wt_code() { 620 | let mut mwg = MarkdownWichtextGenerator::new(10); 621 | mwg.parse("a\n\n soft\n fo\n\nb"); 622 | println!("RES:\n{}", mwg.to_string()); 623 | assert_eq!(mwg.to_string(), "a\n\n [c15:soft]\n [c15: fo]\n\nb\n"); 624 | } 625 | 626 | #[test] 627 | fn check_mkd2wt_inline_wt() { 628 | let mut mwg = MarkdownWichtextGenerator::new(10); 629 | mwg.parse("[Test 123 feio fejwoif jewfo iewjfo iewjf weoi]($c19)"); 630 | println!("RES:\n{}", mwg.to_string()); 631 | assert_eq!(mwg.to_string(), "[c19:Test] [c19:123]\n[c19:feio]\n[c19:fejwoif]\n[c19:jewfo]\n[c19:iewjfo]\n[c19:iewjf] [c19:weoi]\n"); 632 | } 633 | 634 | #[test] 635 | fn check_mkd2wt_inline_raw_wt() { 636 | let mut mwg = MarkdownWichtextGenerator::new(10); 637 | mwg.parse("```wichtext\n [c19:Test fei fjewoif jweofiew joifewwe]\n fioewfijoiwefjewifwe\n```"); 638 | println!("RES:\n{}", mwg.to_string()); 639 | assert_eq!(mwg.to_string(), " [c19:Test fei fjewoif jweofiew joifewwe]\n fioewfijoiwefjewifwe\n"); 640 | } 641 | 642 | #[test] 643 | fn check_mkd2wt_force_break() { 644 | let mut mwg = MarkdownWichtextGenerator::new(10); 645 | mwg.parse("bla \nblo"); 646 | println!("RES:\n{}", mwg.to_string()); 647 | assert_eq!(mwg.to_string(), "bla\nblo\n"); 648 | } 649 | 650 | #[test] 651 | fn check_mkd2wt_image() { 652 | let mut mwg = MarkdownWichtextGenerator::new(10); 653 | mwg.parse("![](node.png)"); 654 | assert_eq!(mwg.to_string(), "[h300Inode.png:]\n"); 655 | 656 | let mut mwg = MarkdownWichtextGenerator::new(10); 657 | mwg.parse("![](node.png?400)"); 658 | assert_eq!(mwg.to_string(), "[h400Inode.png:]\n"); 659 | } 660 | } 661 | -------------------------------------------------------------------------------- /src/painter.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Weird Constructor 2 | // This file is a part of HexoTK. Released under GPL-3.0-or-later. 3 | // See README.md and COPYING for details. 4 | 5 | use super::Rect; 6 | 7 | use std::cell::RefCell; 8 | use std::rc::Rc; 9 | 10 | use std::collections::HashMap; 11 | 12 | use femtovg::{renderer::OpenGl, Canvas, Color, FontId, ImageFlags, ImageId}; 13 | 14 | #[macro_export] 15 | macro_rules! hxclr { 16 | ($i: expr) => { 17 | ( 18 | ($i >> 16 & 0xFF) as f32 / 255.0, 19 | ($i >> 8 & 0xFF) as f32 / 255.0, 20 | ($i & 0xFF) as f32 / 255.0, 21 | ) 22 | }; 23 | } 24 | 25 | #[allow(unused)] 26 | pub fn darken_clr(depth: u32, clr: (f32, f32, f32)) -> (f32, f32, f32) { 27 | if depth == 0 { 28 | return clr; 29 | } 30 | ( 31 | (clr.0 * (1.0 / (1.2_f32).powf(depth as f32))).clamp(0.0, 1.0), 32 | (clr.1 * (1.0 / (1.2_f32).powf(depth as f32))).clamp(0.0, 1.0), 33 | (clr.2 * (1.0 / (1.2_f32).powf(depth as f32))).clamp(0.0, 1.0), 34 | ) 35 | } 36 | 37 | #[allow(unused)] 38 | pub fn lighten_clr(depth: u32, clr: (f32, f32, f32)) -> (f32, f32, f32) { 39 | if depth == 0 { 40 | return clr; 41 | } 42 | ( 43 | (clr.0 * (1.2_f32).powf(depth as f32)).clamp(0.0, 1.0), 44 | (clr.1 * (1.2_f32).powf(depth as f32)).clamp(0.0, 1.0), 45 | (clr.2 * (1.2_f32).powf(depth as f32)).clamp(0.0, 1.0), 46 | ) 47 | } 48 | 49 | //pub fn tpl2clr(clr: (f32, f32, f32)) -> tuix::Color { 50 | // tuix::Color::rgb( 51 | // (clr.0 * 255.0) as u8, 52 | // (clr.1 * 255.0) as u8, 53 | // (clr.2 * 255.0) as u8) 54 | //} 55 | 56 | pub struct ImageStore { 57 | // TODO: FREE THESE IMAGES OR WE HAVE A MEMORY LEAK! 58 | freed_images: Vec, 59 | } 60 | pub struct ImgRef { 61 | store: Rc>, 62 | image_id: ImageId, 63 | w: f32, 64 | h: f32, 65 | } 66 | 67 | impl ImgRef { 68 | pub fn w(&self) -> f32 { 69 | self.w 70 | } 71 | pub fn h(&self) -> f32 { 72 | self.h 73 | } 74 | } 75 | 76 | impl Drop for ImgRef { 77 | fn drop(&mut self) { 78 | self.store.borrow_mut().freed_images.push(self.image_id); 79 | } 80 | } 81 | 82 | pub struct PersistPainterData { 83 | render_targets: Vec, 84 | store: Rc>, 85 | image_files: HashMap, 86 | image_data: HashMap>, 87 | } 88 | impl PersistPainterData { 89 | pub fn new() -> Self { 90 | Self { 91 | render_targets: vec![], 92 | store: Rc::new(RefCell::new(ImageStore { freed_images: vec![] })), 93 | image_files: HashMap::new(), 94 | image_data: HashMap::new(), 95 | } 96 | } 97 | 98 | pub fn preload_image(&mut self, file: &str, data: Vec) { 99 | self.image_data.insert(file.to_string(), data); 100 | } 101 | 102 | pub fn get_image_file(&mut self, file: &str, canvas: &mut Canvas) -> Option { 103 | if let Some(image_id) = self.image_files.get(file) { 104 | Some(*image_id) 105 | } else { 106 | let image_data = self.image_data.get(file)?; 107 | 108 | match canvas.load_image_mem(&image_data[..], ImageFlags::empty()) { 109 | Ok(img_id) => { 110 | self.image_files.insert(file.to_string(), img_id); 111 | Some(img_id) 112 | } 113 | Err(e) => { 114 | eprintln!("Error loading image: {}", e); 115 | None 116 | } 117 | } 118 | } 119 | } 120 | 121 | pub fn init_render_targets(&mut self, target: femtovg::RenderTarget) { 122 | self.render_targets.clear(); 123 | self.render_targets.push(target); 124 | } 125 | 126 | pub fn cleanup(&self, canvas: &mut Canvas) { 127 | let mut store = self.store.borrow_mut(); 128 | if !store.freed_images.is_empty() { 129 | for img in store.freed_images.iter() { 130 | canvas.delete_image(*img); 131 | //d// println!("CLEANUP IMAGE!"); 132 | } 133 | 134 | store.freed_images.clear(); 135 | } 136 | } 137 | } 138 | 139 | #[derive(Debug, Clone, Copy)] 140 | pub struct LblDebugTag { 141 | wid_id: usize, 142 | x: i32, 143 | y: i32, 144 | offs_x: f32, 145 | offs_y: f32, 146 | source: &'static str, 147 | } 148 | 149 | impl LblDebugTag { 150 | pub fn new(wid_id: usize, x: i32, y: i32, source: &'static str) -> Self { 151 | Self { wid_id, x, y, offs_x: 0.0, offs_y: 0.0, source } 152 | } 153 | 154 | pub fn from_id(wid_id: usize) -> Self { 155 | Self { wid_id, x: 0, y: 0, offs_x: 0.0, offs_y: 0.0, source: "?" } 156 | } 157 | 158 | pub fn info(&self) -> (usize, &'static str, (i32, i32)) { 159 | (self.wid_id, self.source, (self.x, self.y)) 160 | } 161 | 162 | pub fn set_logic_pos(&mut self, x: i32, y: i32) { 163 | self.x = x; 164 | self.y = y; 165 | } 166 | 167 | pub fn set_offs(&mut self, offs: (f32, f32)) { 168 | self.offs_x = offs.0; 169 | self.offs_y = offs.1; 170 | } 171 | 172 | pub fn offs_src(&mut self, offs: (f32, f32), src: &'static str) -> &mut Self { 173 | self.offs_x = offs.0; 174 | self.offs_y = offs.1; 175 | self.source = src; 176 | self 177 | } 178 | 179 | pub fn source(&mut self, src: &'static str) -> &mut Self { 180 | self.source = src; 181 | self 182 | } 183 | } 184 | 185 | pub struct Painter<'a, 'b> { 186 | pub canvas: &'a mut Canvas, 187 | pub data: &'b mut PersistPainterData, 188 | pub lbl_collect: Option>, 189 | pub font: FontId, 190 | pub font_mono: FontId, 191 | pub dpi_factor: f32, 192 | } 193 | 194 | fn color_paint(color: (f32, f32, f32)) -> femtovg::Paint { 195 | femtovg::Paint::color(Color::rgbf(color.0 as f32, color.1 as f32, color.2 as f32)) 196 | } 197 | 198 | impl<'a, 'b> Painter<'a, 'b> { 199 | pub fn start_label_collector(&mut self) { 200 | self.lbl_collect = Some(vec![]); 201 | } 202 | 203 | pub fn needs_labels(&self) -> bool { 204 | self.lbl_collect.is_some() 205 | } 206 | 207 | pub fn get_label_collection( 208 | &mut self, 209 | ) -> Option> { 210 | self.lbl_collect.take() 211 | } 212 | 213 | pub fn new_image(&mut self, w: f32, h: f32) -> ImgRef { 214 | let image_id = self 215 | .canvas 216 | .create_image_empty( 217 | w as usize, 218 | h as usize, 219 | femtovg::PixelFormat::Rgba8, 220 | femtovg::ImageFlags::FLIP_Y, 221 | ) 222 | .expect("making image buffer"); 223 | 224 | //d// println!("new_image w={}, h={} id={:?}", w, h, image_id); 225 | 226 | ImgRef { store: self.data.store.clone(), w, h, image_id } 227 | } 228 | 229 | pub fn start_image(&mut self, image: &ImgRef) { 230 | //d// println!("start_image {:?}", image.image_id); 231 | self.canvas.save(); 232 | self.canvas.set_render_target(femtovg::RenderTarget::Image(image.image_id)); 233 | self.data.render_targets.push(femtovg::RenderTarget::Image(image.image_id)); 234 | self.canvas.clear_rect( 235 | 0, 236 | 0, 237 | image.w as u32, 238 | image.h as u32, 239 | Color::rgbaf(0.0, 0.0, 0.0, 0.0), 240 | ); 241 | } 242 | 243 | pub fn finish_image(&mut self) { 244 | //d// println!("finish_image"); 245 | self.canvas.flush(); 246 | self.canvas.restore(); 247 | self.data.render_targets.pop(); 248 | if let Some(rt) = self.data.render_targets.last() { 249 | self.canvas.set_render_target(*rt); 250 | } 251 | } 252 | 253 | pub fn draw_image(&mut self, image: &ImgRef, screen_x: f32, screen_y: f32) { 254 | //d// println!("draw_image id={:?} x={}, y={}, w={}, h={}", 255 | //d// image.image_id, 256 | //d// screen_x, 257 | //d// screen_y, 258 | //d// image.w, 259 | //d// image.h); 260 | let img_paint = femtovg::Paint::image( 261 | image.image_id, 262 | screen_x, 263 | screen_y, 264 | image.w as f32, 265 | image.h as f32, 266 | 0.0, 267 | 1.0, 268 | ); 269 | let mut path = femtovg::Path::new(); 270 | path.rect(screen_x as f32, screen_y as f32, image.w as f32, image.h as f32); 271 | self.canvas.fill_path(&mut path, &img_paint); 272 | } 273 | 274 | pub fn draw_image_file(&mut self, file: &str, x: f32, y: f32, _w: f32, h: f32) { 275 | if let Some(img_id) = self.data.get_image_file(file, self.canvas) { 276 | if let Ok((imgw, imgh)) = self.canvas.image_size(img_id) { 277 | if imgh as f32 > 0.0 && h > 0.0 { 278 | let w = (imgw as f32 * h) / imgh as f32; 279 | 280 | let img_paint = 281 | femtovg::Paint::image(img_id, x, y, w as f32, h as f32, 0.0, 1.0); 282 | let mut path = femtovg::Path::new(); 283 | path.rect(x, y, w, h); 284 | self.canvas.fill_path(&mut path, &img_paint); 285 | } 286 | } 287 | } 288 | } 289 | 290 | #[allow(unused_variables)] 291 | fn label_with_font( 292 | &mut self, 293 | size: f32, 294 | align: i8, 295 | rot: f32, 296 | color: (f32, f32, f32), 297 | x: f32, 298 | y: f32, 299 | xoi: f32, 300 | yoi: f32, 301 | w: f32, 302 | h: f32, 303 | text: &str, 304 | font: FontId, 305 | dbg: &LblDebugTag, 306 | ) { 307 | let mut paint = color_paint(color); 308 | paint.set_font(&[font]); 309 | paint.set_font_size(size as f32); 310 | paint.set_text_baseline(femtovg::Baseline::Middle); 311 | let x = x.round(); 312 | 313 | let (x, y) = if rot > 0.0 { 314 | self.canvas.save(); 315 | let x = x as f32; 316 | let y = y as f32; 317 | let wh = (w / 2.0) as f32; 318 | let hh = (h / 2.0) as f32; 319 | 320 | let rot = rot.to_radians() as f32; 321 | 322 | self.canvas.translate(x + wh, y + hh); 323 | self.canvas.rotate(rot); 324 | self.canvas.translate(xoi as f32, yoi as f32); 325 | 326 | (-wh, -hh) 327 | } else { 328 | (x, y) 329 | }; 330 | 331 | // let mut p = femtovg::Path::new(); 332 | // p.rect(x as f32, y as f32, w as f32, h as f32); 333 | // self.canvas.stroke_path(&mut p, paint); 334 | let (rx, ry) = match align { 335 | -1 => { 336 | paint.set_text_align(femtovg::Align::Left); 337 | (x as f32, (y + h / 2.0).round() as f32) 338 | } 339 | 0 => { 340 | paint.set_text_align(femtovg::Align::Center); 341 | ((x + (w / 2.0)) as f32, (y + h / 2.0).round() as f32) 342 | } 343 | _ => { 344 | paint.set_text_align(femtovg::Align::Right); 345 | ((x + w) as f32, (y + h / 2.0).round() as f32) 346 | } 347 | }; 348 | 349 | let _ = self.canvas.fill_text(rx, ry, text, &paint); 350 | 351 | if let Some(collector) = &mut self.lbl_collect { 352 | if let Ok(metr) = self.canvas.measure_text(rx, ry, text, &paint) { 353 | collector.push(( 354 | *dbg, 355 | ( 356 | rx + dbg.offs_x, 357 | ry + dbg.offs_y, 358 | metr.width(), 359 | metr.height(), 360 | text.to_string(), 361 | ), 362 | )); 363 | } 364 | } 365 | 366 | // let mut p = femtovg::Path::new(); 367 | // let mut paint2 = color_paint((1.0, 1.0, 1.0)); 368 | // p.rect((x - 1.0) as f32, (y - 1.0) as f32, 2.0, 2.0); 369 | // p.rect(((x + 0.5 * w) - 1.0) as f32, ((y + 0.5 * h) - 1.0) as f32, 2.0, 2.0); 370 | // self.canvas.stroke_path(&mut p, paint2); 371 | 372 | if rot > 0.0 { 373 | // self.canvas.translate(-(0.5 * w) as f32, 0.0); 374 | self.canvas.restore(); 375 | } 376 | } 377 | } 378 | 379 | impl<'a, 'b> Painter<'a, 'b> { 380 | pub fn clip_region(&mut self, x: f32, y: f32, w: f32, h: f32) { 381 | self.canvas.save(); 382 | self.canvas.scissor(x as f32, y as f32, w as f32, h as f32); 383 | } 384 | 385 | pub fn reset_clip_region(&mut self) { 386 | self.canvas.reset_scissor(); 387 | self.canvas.restore(); 388 | } 389 | 390 | pub fn path_fill_rot( 391 | &mut self, 392 | color: (f32, f32, f32), 393 | rot: f32, 394 | x: f32, 395 | y: f32, 396 | xo: f32, 397 | yo: f32, 398 | segments: &mut dyn std::iter::Iterator, 399 | closed: bool, 400 | ) { 401 | self.canvas.save(); 402 | let rot = rot.to_radians(); 403 | 404 | self.canvas.translate(x as f32, y as f32); 405 | self.canvas.rotate(rot as f32); 406 | self.canvas.translate(xo as f32, yo as f32); 407 | 408 | self.path_fill(color, segments, closed); 409 | 410 | self.canvas.restore(); 411 | } 412 | 413 | #[allow(dead_code)] 414 | pub fn path_stroke_rot( 415 | &mut self, 416 | width: f32, 417 | color: (f32, f32, f32), 418 | rot: f32, 419 | x: f32, 420 | y: f32, 421 | xo: f32, 422 | yo: f32, 423 | segments: &mut dyn std::iter::Iterator, 424 | closed: bool, 425 | ) { 426 | self.canvas.save(); 427 | let rot = rot.to_radians(); 428 | 429 | self.canvas.translate(x as f32, y as f32); 430 | self.canvas.rotate(rot as f32); 431 | self.canvas.translate(xo as f32, yo as f32); 432 | 433 | self.path_stroke(width, color, segments, closed); 434 | 435 | self.canvas.restore(); 436 | } 437 | 438 | pub fn path_fill( 439 | &mut self, 440 | color: (f32, f32, f32), 441 | segments: &mut dyn std::iter::Iterator, 442 | closed: bool, 443 | ) { 444 | let mut p = femtovg::Path::new(); 445 | let paint = color_paint(color); 446 | 447 | let mut first = true; 448 | for s in segments { 449 | if first { 450 | p.move_to(s.0 as f32, s.1 as f32); 451 | first = false; 452 | } else { 453 | p.line_to(s.0 as f32, s.1 as f32); 454 | } 455 | } 456 | 457 | if closed { 458 | p.close(); 459 | } 460 | 461 | self.canvas.fill_path(&mut p, &paint); 462 | } 463 | 464 | pub fn stroke( 465 | &mut self, 466 | width: f32, 467 | color: (f32, f32, f32), 468 | segments: &[(f32, f32)], 469 | closed: bool, 470 | ) { 471 | self.path_stroke(width, color, &mut segments.iter().copied(), closed); 472 | } 473 | 474 | pub fn path_stroke( 475 | &mut self, 476 | width: f32, 477 | color: (f32, f32, f32), 478 | segments: &mut dyn std::iter::Iterator, 479 | closed: bool, 480 | ) { 481 | let mut p = femtovg::Path::new(); 482 | let mut paint = color_paint(color); 483 | paint.set_line_join(femtovg::LineJoin::Round); 484 | // paint.set_line_cap(femtovg::LineCap::Round); 485 | paint.set_line_width(width as f32); 486 | 487 | let mut first = true; 488 | for s in segments { 489 | if first { 490 | p.move_to(s.0 as f32, s.1 as f32); 491 | first = false; 492 | } else { 493 | p.line_to(s.0 as f32, s.1 as f32); 494 | } 495 | } 496 | 497 | if closed { 498 | p.close(); 499 | } 500 | 501 | self.canvas.stroke_path(&mut p, &paint); 502 | } 503 | 504 | pub fn arc_stroke( 505 | &mut self, 506 | width: f32, 507 | color: (f32, f32, f32), 508 | radius: f32, 509 | from_rad: f32, 510 | to_rad: f32, 511 | x: f32, 512 | y: f32, 513 | ) { 514 | let mut p = femtovg::Path::new(); 515 | let mut paint = color_paint(color); 516 | paint.set_line_width(width as f32); 517 | p.arc( 518 | x as f32, 519 | y as f32, 520 | radius as f32, 521 | from_rad as f32, 522 | to_rad as f32, 523 | femtovg::Solidity::Hole, 524 | ); 525 | self.canvas.stroke_path(&mut p, &paint); 526 | } 527 | 528 | pub fn rect_border_fill_r( 529 | &mut self, 530 | border: f32, 531 | color: (f32, f32, f32), 532 | bg_color: (f32, f32, f32), 533 | rect: Rect, 534 | ) { 535 | self.rect_fill(color, rect.x, rect.y, rect.w, rect.h); 536 | let rect = rect.shrink(border, border); 537 | self.rect_fill(bg_color, rect.x, rect.y, rect.w, rect.h); 538 | } 539 | 540 | pub fn rect_fill_r(&mut self, color: (f32, f32, f32), rect: Rect) { 541 | self.rect_fill(color, rect.x, rect.y, rect.w, rect.h) 542 | } 543 | 544 | pub fn rect_fill(&mut self, color: (f32, f32, f32), x: f32, y: f32, w: f32, h: f32) { 545 | let mut pth = femtovg::Path::new(); 546 | pth.rect(x as f32, y as f32, w as f32, h as f32); 547 | self.canvas.fill_path(&mut pth, &color_paint(color)); 548 | } 549 | 550 | pub fn rect_stroke_r(&mut self, width: f32, color: (f32, f32, f32), r: Rect) { 551 | self.rect_stroke(width, color, r.x, r.y, r.w, r.h) 552 | } 553 | 554 | pub fn rect_stroke( 555 | &mut self, 556 | width: f32, 557 | color: (f32, f32, f32), 558 | x: f32, 559 | y: f32, 560 | w: f32, 561 | h: f32, 562 | ) { 563 | let mut pth = femtovg::Path::new(); 564 | pth.rect(x as f32, y as f32, w as f32, h as f32); 565 | let mut paint = color_paint(color); 566 | paint.set_line_width(width as f32); 567 | self.canvas.stroke_path(&mut pth, &paint); 568 | } 569 | 570 | pub fn label( 571 | &mut self, 572 | size: f32, 573 | align: i8, 574 | color: (f32, f32, f32), 575 | x: f32, 576 | y: f32, 577 | w: f32, 578 | h: f32, 579 | text: &str, 580 | dbg: &LblDebugTag, 581 | ) { 582 | self.label_with_font(size, align, 0.0, color, x, y, 0.0, 0.0, w, h, text, self.font, dbg); 583 | } 584 | 585 | pub fn label_rot( 586 | &mut self, 587 | size: f32, 588 | align: i8, 589 | rot: f32, 590 | color: (f32, f32, f32), 591 | x: f32, 592 | y: f32, 593 | xo: f32, 594 | yo: f32, 595 | w: f32, 596 | h: f32, 597 | text: &str, 598 | dbg: &LblDebugTag, 599 | ) { 600 | self.label_with_font(size, align, rot, color, x, y, xo, yo, w, h, text, self.font, dbg); 601 | } 602 | 603 | pub fn label_mono( 604 | &mut self, 605 | size: f32, 606 | align: i8, 607 | color: (f32, f32, f32), 608 | x: f32, 609 | y: f32, 610 | w: f32, 611 | h: f32, 612 | text: &str, 613 | dbg: &LblDebugTag, 614 | ) { 615 | self.label_with_font( 616 | size, 617 | align, 618 | 0.0, 619 | color, 620 | x, 621 | y, 622 | 0.0, 623 | 0.0, 624 | w, 625 | h, 626 | text, 627 | self.font_mono, 628 | dbg, 629 | ); 630 | } 631 | 632 | pub fn text_width(&mut self, size: f32, mono: bool, text: &str) -> f32 { 633 | let mut paint = color_paint((1.0, 0.0, 1.0)); 634 | if mono { 635 | paint.set_font(&[self.font_mono]); 636 | } else { 637 | paint.set_font(&[self.font]); 638 | } 639 | paint.set_font_size(size); 640 | if let Ok(metr) = self.canvas.measure_text(0.0, 0.0, text, &paint) { 641 | metr.width() 642 | } else { 643 | 20.0 644 | } 645 | } 646 | 647 | pub fn font_height(&mut self, size: f32, mono: bool) -> f32 { 648 | let mut paint = color_paint((1.0, 0.0, 1.0)); 649 | if mono { 650 | paint.set_font(&[self.font_mono]); 651 | } else { 652 | paint.set_font(&[self.font]); 653 | } 654 | paint.set_font_size(size); 655 | if let Ok(metr) = self.canvas.measure_font(&paint) { 656 | metr.height() 657 | } else { 658 | UI_ELEM_TXT_DEFAULT_H as f32 659 | } 660 | } 661 | 662 | pub fn define_debug_area (LblDebugTag, String)>(&mut self, pos: Rect, mut f: F) { 663 | if let Some(collector) = &mut self.lbl_collect { 664 | let (dbg, text) = f(); 665 | collector.push((dbg, (pos.x + dbg.offs_x, pos.y + dbg.offs_y, pos.w, pos.h, text))); 666 | } 667 | } 668 | 669 | pub fn translate(&mut self, x: f32, y: f32) { 670 | // , x2: f64, y2: f64, factor: f64) { 671 | self.canvas.save(); 672 | // self.cur_scale = factor as f32; 673 | // let factor = self.cur_scale; 674 | // self.canvas.translate(x as f32, y as f32); 675 | self.canvas.translate(x, y); 676 | // self.canvas.scale(factor, factor); 677 | // self.canvas.translate(x2 as f32, y2 as f32); 678 | // self.canvas.translate(-x as f32 / factor, -y as f32 / factor); 679 | } 680 | 681 | pub fn restore(&mut self) { 682 | self.canvas.restore(); 683 | } 684 | } 685 | 686 | #[allow(unused)] 687 | pub fn calc_font_size_from_text( 688 | p: &mut Painter, 689 | txt: &str, 690 | mut max_fs: f32, 691 | max_width: f32, 692 | ) -> f32 { 693 | while p.text_width(max_fs, false, txt) > max_width { 694 | let step = (max_fs * 0.1).max(0.1); 695 | max_fs -= step; 696 | 697 | if max_fs < 1.0 { 698 | break; 699 | } 700 | } 701 | 702 | max_fs 703 | } 704 | 705 | pub const UI_ELEM_TXT_DEFAULT_H: f32 = 16.0; 706 | -------------------------------------------------------------------------------- /src/rect.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Weird Constructor 2 | // This file is a part of HexoTK. Released under GPL-3.0-or-later. 3 | // See README.md and COPYING for details. 4 | 5 | #[derive(Debug, Clone, Copy, PartialEq)] 6 | pub struct Rect { 7 | pub x: f32, 8 | pub y: f32, 9 | pub w: f32, 10 | pub h: f32, 11 | } 12 | 13 | #[allow(dead_code)] 14 | impl Rect { 15 | pub fn from_tpl(t: (f32, f32, f32, f32)) -> Self { 16 | Self { x: t.0, y: t.1, w: t.2, h: t.3 } 17 | } 18 | 19 | pub fn from(x: f32, y: f32, w: f32, h: f32) -> Self { 20 | Self { x, y, w, h } 21 | } 22 | 23 | pub fn clip_wh(&self) -> Self { 24 | Self { x: self.x, y: self.y, w: self.w.max(0.0), h: self.h.max(0.0) } 25 | } 26 | 27 | pub fn round(&self) -> Self { 28 | Self { x: self.x.round(), y: self.y.round(), w: self.w.round(), h: self.h.round() } 29 | } 30 | 31 | pub fn floor(&self) -> Self { 32 | Self { x: self.x.floor(), y: self.y.floor(), w: self.w.floor(), h: self.h.floor() } 33 | } 34 | 35 | pub fn resize(&self, w: f32, h: f32) -> Self { 36 | Self { x: self.x, y: self.y, w, h } 37 | } 38 | 39 | pub fn scale(&self, factor: f32) -> Self { 40 | Self { x: self.x, y: self.y, w: self.w * factor, h: self.h * factor } 41 | } 42 | 43 | pub fn center(&self) -> Self { 44 | Self { x: self.x + self.w * 0.5, y: self.y + self.h * 0.5, w: 1.0, h: 1.0 } 45 | } 46 | 47 | pub fn crop_left(&self, delta: f32) -> Self { 48 | Self { x: self.x + delta, y: self.y, w: self.w - delta, h: self.h } 49 | } 50 | 51 | pub fn crop_right(&self, delta: f32) -> Self { 52 | Self { x: self.x, y: self.y, w: self.w - delta, h: self.h } 53 | } 54 | 55 | pub fn crop_bottom(&self, delta: f32) -> Self { 56 | Self { x: self.x, y: self.y, w: self.w, h: self.h - delta } 57 | } 58 | 59 | pub fn crop_top(&self, delta: f32) -> Self { 60 | Self { x: self.x, y: self.y + delta, w: self.w, h: self.h - delta } 61 | } 62 | 63 | pub fn crop(&self, left: f32, right: f32, top: f32, bottom: f32) -> Self { 64 | self.crop_left(left).crop_right(right).crop_top(top).crop_bottom(bottom) 65 | } 66 | 67 | pub fn shrink(&self, delta_x: f32, delta_y: f32) -> Self { 68 | Self { 69 | x: self.x + delta_x, 70 | y: self.y + delta_y, 71 | w: self.w - 2.0 * delta_x, 72 | h: self.h - 2.0 * delta_y, 73 | } 74 | } 75 | 76 | pub fn grow(&self, delta_x: f32, delta_y: f32) -> Self { 77 | Self { 78 | x: self.x - delta_x, 79 | y: self.y - delta_y, 80 | w: self.w + 2.0 * delta_x, 81 | h: self.h + 2.0 * delta_y, 82 | } 83 | } 84 | 85 | pub fn offs(&self, x: f32, y: f32) -> Self { 86 | Self { x: self.x + x, y: self.y + y, w: self.w, h: self.h } 87 | } 88 | 89 | pub fn move_into(mut self, pos: &Rect) -> Self { 90 | if self.x < pos.x { 91 | self.x = pos.x; 92 | } 93 | if self.y < pos.y { 94 | self.y = pos.y; 95 | } 96 | 97 | if (self.x + self.w) > (pos.x + pos.w) { 98 | self.x = (pos.x + pos.w) - self.w; 99 | } 100 | 101 | if (self.y + self.h) > (pos.y + pos.h) { 102 | self.y = (pos.y + pos.h) - self.h; 103 | } 104 | 105 | self 106 | } 107 | 108 | pub fn aabb_is_inside(&self, aabb: Rect) -> bool { 109 | if self.is_inside(aabb.x, aabb.y) { 110 | return true; 111 | } 112 | if self.is_inside(aabb.x + aabb.w, aabb.y) { 113 | return true; 114 | } 115 | if self.is_inside(aabb.x, aabb.y + aabb.h) { 116 | return true; 117 | } 118 | if self.is_inside(aabb.x + aabb.w, aabb.y + aabb.h) { 119 | return true; 120 | } 121 | false 122 | } 123 | 124 | pub fn is_inside(&self, x: f32, y: f32) -> bool { 125 | x >= self.x && x <= (self.x + self.w) && y >= self.y && y <= (self.y + self.h) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/style.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Weird Constructor 2 | // This file is a part of HexoTK. Released under GPL-3.0-or-later. 3 | // See README.md and COPYING for details. 4 | 5 | use crate::hxclr; 6 | use crate::Rect; 7 | 8 | use std::rc::Rc; 9 | 10 | pub const UI_BOX_H: f32 = 200.0; 11 | pub const UI_BOX_BORD: f32 = 2.0; 12 | pub const UI_MARGIN: f32 = 4.0; 13 | pub const UI_PADDING: f32 = 6.0; 14 | pub const UI_ELEM_TXT_H: f32 = 16.0; 15 | pub const UI_SAFETY_PAD: f32 = 1.0; 16 | 17 | pub const UI_BG_CLR: (f32, f32, f32) = hxclr!(0x414a51); // 473f49 18 | pub const UI_BG2_CLR: (f32, f32, f32) = hxclr!(0x4b535a); // 594f5d 19 | pub const UI_BG3_CLR: (f32, f32, f32) = hxclr!(0x545b61); // 645868 20 | pub const UI_TXT_CLR: (f32, f32, f32) = hxclr!(0xdcdcf0); 21 | pub const UI_BORDER_CLR: (f32, f32, f32) = hxclr!(0x163239); // 2b0530); 22 | pub const UI_LBL_BG_CLR: (f32, f32, f32) = hxclr!(0x111920); // hxclr!(0x16232f); // 1a2733); // 200e1f); 23 | pub const UI_LBL_BG_ALT_CLR: (f32, f32, f32) = hxclr!(0x2d4d5e); // 323237 24 | pub const UI_ACCENT_CLR: (f32, f32, f32) = hxclr!(0x922f93); // b314aa); 25 | pub const UI_ACCENT_DARK_CLR: (f32, f32, f32) = hxclr!(0x1e333d); // 4d184a); // 4d184a); 26 | pub const UI_ACCENT_BG1_CLR: (f32, f32, f32) = hxclr!(0x111920); // UI_LBL_BG_CLR; // hxclr!(0x111920); // UI_LBL_BG_CLR; // hxclr!(0x27091b); // 381c38); // 200e1f); 27 | pub const UI_ACCENT_BG2_CLR: (f32, f32, f32) = hxclr!(0x192129); // 2c132a); 28 | pub const UI_PRIM_CLR: (f32, f32, f32) = hxclr!(0x03fdcb); // 69e8ed 29 | pub const UI_PRIM2_CLR: (f32, f32, f32) = hxclr!(0x228f9d); // 1aaeb3 30 | pub const UI_HLIGHT_CLR: (f32, f32, f32) = hxclr!(0xecf9ce); // e9f840 31 | pub const UI_HLIGHT2_CLR: (f32, f32, f32) = hxclr!(0xbcf9cd); // b5c412 32 | pub const UI_SELECT_CLR: (f32, f32, f32) = hxclr!(0xd73988); // 0xdc1821); 33 | pub const UI_INACTIVE_CLR: (f32, f32, f32) = hxclr!(0x6f8782); 34 | pub const UI_INACTIVE2_CLR: (f32, f32, f32) = hxclr!(0xa6dbd0); 35 | 36 | pub fn get_ui_colors() -> Vec<(&'static str, (f32, f32, f32))> { 37 | vec![ 38 | ("UI_BG_CLR", UI_BG_CLR), 39 | ("UI_BG2_CLR", UI_BG2_CLR), 40 | ("UI_BG3_CLR", UI_BG3_CLR), 41 | ("UI_TXT_CLR", UI_TXT_CLR), 42 | ("UI_BORDER_CLR", UI_BORDER_CLR), 43 | ("UI_LBL_BG_CLR", UI_LBL_BG_CLR), 44 | ("UI_LBL_BG_ALT_CLR", UI_LBL_BG_ALT_CLR), 45 | ("UI_ACCENT_CLR", UI_ACCENT_CLR), 46 | ("UI_ACCENT_DARK_CLR", UI_ACCENT_DARK_CLR), 47 | ("UI_ACCENT_BG1_CLR", UI_ACCENT_BG1_CLR), 48 | ("UI_ACCENT_BG2_CLR", UI_ACCENT_BG2_CLR), 49 | ("UI_PRIM_CLR", UI_PRIM_CLR), 50 | ("UI_PRIM2_CLR", UI_PRIM2_CLR), 51 | ("UI_HLIGHT_CLR", UI_HLIGHT_CLR), 52 | ("UI_HLIGHT2_CLR", UI_HLIGHT2_CLR), 53 | ("UI_SELECT_CLR", UI_SELECT_CLR), 54 | ("UI_INACTIVE_CLR", UI_INACTIVE_CLR), 55 | ("UI_INACTIVE2_CLR", UI_INACTIVE2_CLR), 56 | ] 57 | } 58 | 59 | pub fn get_standard_colors() -> Vec<(f32, f32, f32)> { 60 | vec![ 61 | hxclr!(0x922f93), // 0 62 | hxclr!(0x862b37), 63 | hxclr!(0xb45745), 64 | hxclr!(0x835933), 65 | hxclr!(0xa69b64), 66 | hxclr!(0xbec8a6), 67 | hxclr!(0x346c38), // 6 68 | hxclr!(0x1fb349), 69 | hxclr!(0x4cdb80), 70 | hxclr!(0x59bca3), 71 | hxclr!(0x228f9d), 72 | hxclr!(0x03b5e7), 73 | hxclr!(0x3b5eca), // 12 74 | hxclr!(0x594fa1), 75 | hxclr!(0xc2b2eb), 76 | hxclr!(0xac70fa), 77 | hxclr!(0x9850a9), 78 | hxclr!(0xdc4fc1), // 17 79 | hxclr!(0x03fdcb), // 18 80 | ] 81 | } 82 | 83 | #[derive(Debug, Clone, Copy)] 84 | pub enum Align { 85 | Center, 86 | Left, 87 | Right, 88 | } 89 | 90 | #[derive(Debug, Clone, Copy)] 91 | pub enum VAlign { 92 | Middle, 93 | Top, 94 | Bottom, 95 | } 96 | 97 | #[derive(Debug, Clone, Copy)] 98 | pub enum BorderStyle { 99 | Rect, 100 | Hex { offset: f32 }, 101 | Bevel { corner_offsets: (f32, f32, f32, f32) }, 102 | } 103 | 104 | #[derive(Debug, Clone)] 105 | pub enum StyleExt { 106 | None, 107 | Graph { 108 | graph_line: f32, 109 | vline1: f32, 110 | vline2: f32, 111 | vline1_color: (f32, f32, f32), 112 | vline2_color: (f32, f32, f32), 113 | hline: f32, 114 | hline_color: (f32, f32, f32), 115 | }, 116 | PatternEditor { 117 | row_height: f32, 118 | col_width: f32, 119 | col_div_pad: f32, 120 | }, 121 | BlockCode { 122 | with_markers: bool, 123 | grid_marker_color: (f32, f32, f32), 124 | block_bg_hover_color: (f32, f32, f32), 125 | block_bg_color: (f32, f32, f32), 126 | port_select_color: (f32, f32, f32), 127 | }, 128 | } 129 | 130 | #[derive(Debug, Clone)] 131 | pub struct Style { 132 | pub border_style: BorderStyle, 133 | pub bg_color: (f32, f32, f32), 134 | pub border_color: (f32, f32, f32), 135 | pub border2_color: (f32, f32, f32), 136 | pub color: (f32, f32, f32), 137 | pub color2: (f32, f32, f32), 138 | pub border: f32, 139 | pub border2: f32, 140 | pub line: f32, 141 | pub pad_left: f32, 142 | pub pad_right: f32, 143 | pub pad_top: f32, 144 | pub pad_bottom: f32, 145 | pub pad_item: f32, 146 | pub shadow_offs: (f32, f32), 147 | pub shadow_color: (f32, f32, f32), 148 | pub hover_shadow_color: (f32, f32, f32), 149 | pub hover_border_color: (f32, f32, f32), 150 | pub hover_color: (f32, f32, f32), 151 | pub active_shadow_color: (f32, f32, f32), 152 | pub active_border_color: (f32, f32, f32), 153 | pub active_color: (f32, f32, f32), 154 | pub inactive_color: (f32, f32, f32), 155 | pub selected_color: (f32, f32, f32), 156 | pub text_align: Align, 157 | pub text_valign: VAlign, 158 | pub font_size: f32, 159 | pub colors: Vec<(f32, f32, f32)>, 160 | pub ext: StyleExt, 161 | } 162 | 163 | impl Style { 164 | pub fn new() -> Self { 165 | let colors = get_standard_colors(); 166 | 167 | Self { 168 | bg_color: UI_BG_CLR, 169 | border_color: UI_BORDER_CLR, 170 | border2_color: UI_ACCENT_CLR, 171 | color: UI_PRIM_CLR, 172 | color2: UI_PRIM2_CLR, 173 | border: UI_BOX_BORD, 174 | border2: UI_BOX_BORD, 175 | line: 2.0 * UI_BOX_BORD, 176 | border_style: BorderStyle::Rect, 177 | pad_left: 0.0, 178 | pad_right: 0.0, 179 | pad_top: 0.0, 180 | pad_bottom: 0.0, 181 | pad_item: 0.0, 182 | shadow_offs: (0.0, 0.0), 183 | shadow_color: UI_PRIM_CLR, 184 | hover_shadow_color: UI_SELECT_CLR, 185 | hover_border_color: UI_HLIGHT_CLR, 186 | hover_color: UI_HLIGHT_CLR, 187 | active_shadow_color: UI_HLIGHT_CLR, 188 | active_border_color: UI_SELECT_CLR, 189 | active_color: UI_HLIGHT2_CLR, 190 | inactive_color: UI_INACTIVE_CLR, 191 | selected_color: UI_SELECT_CLR, 192 | text_align: Align::Center, 193 | text_valign: VAlign::Middle, 194 | font_size: 14.0, 195 | ext: StyleExt::None, 196 | colors, 197 | } 198 | } 199 | 200 | pub fn apply_padding(&self, dpi_f: f32, pos: Rect) -> Rect { 201 | Rect { 202 | x: pos.x + dpi_f * self.pad_left, 203 | y: pos.y + dpi_f * self.pad_top, 204 | w: pos.w - dpi_f * (self.pad_left + self.pad_right), 205 | h: pos.h - dpi_f * (self.pad_top + self.pad_bottom), 206 | } 207 | } 208 | 209 | pub fn with_style_clone(&self, f: F) -> Rc { 210 | let mut clone = self.clone(); 211 | f(&mut clone); 212 | Rc::new(clone) 213 | } 214 | 215 | pub fn color_by_idx(&self, idx: usize) -> (f32, f32, f32) { 216 | self.colors[idx % self.colors.len()] 217 | } 218 | 219 | pub fn choose_shadow_color(&self, is_active: bool, is_hovered: bool) -> (f32, f32, f32) { 220 | if is_active { 221 | self.active_shadow_color 222 | } else if is_hovered { 223 | self.hover_shadow_color 224 | } else { 225 | self.shadow_color 226 | } 227 | } 228 | 229 | pub fn choose_border_color(&self, is_active: bool, is_hovered: bool) -> (f32, f32, f32) { 230 | if is_active { 231 | self.active_border_color 232 | } else if is_hovered { 233 | self.hover_border_color 234 | } else { 235 | self.border_color 236 | } 237 | } 238 | 239 | pub fn choose_color(&self, is_active: bool, is_hovered: bool) -> (f32, f32, f32) { 240 | if is_active { 241 | self.active_color 242 | } else if is_hovered { 243 | self.hover_color 244 | } else { 245 | self.color 246 | } 247 | } 248 | } 249 | 250 | pub struct DPIStyle<'a> { 251 | style: &'a Style, 252 | dpi_factor: f32, 253 | } 254 | 255 | macro_rules! dpi_accessor { 256 | ($field: ident) => { 257 | pub fn $field(&self) -> f32 { 258 | self.style.$field * self.dpi_factor 259 | } 260 | }; 261 | } 262 | 263 | macro_rules! dpi_ext_accessor { 264 | ($enum: ident :: $opt: ident, $field: ident, $default: expr) => { 265 | pub fn $field(&self) -> f32 { 266 | if let $enum::$opt { $field, .. } = &self.style.ext { 267 | $field * self.dpi_factor 268 | } else { 269 | $default * self.dpi_factor 270 | } 271 | } 272 | }; 273 | } 274 | 275 | macro_rules! color_accessor { 276 | ($field: ident) => { 277 | pub fn $field(&self) -> (f32, f32, f32) { 278 | self.style.$field 279 | } 280 | }; 281 | } 282 | 283 | macro_rules! color_ext_accessor { 284 | ($enum: ident :: $opt: ident, $field: ident, $default: expr) => { 285 | pub fn $field(&self) -> (f32, f32, f32) { 286 | if let $enum::$opt { $field, .. } = &self.style.ext { 287 | *$field 288 | } else { 289 | $default 290 | } 291 | } 292 | }; 293 | } 294 | 295 | macro_rules! bool_ext_accessor { 296 | ($enum: ident :: $opt: ident, $field: ident, $default: expr) => { 297 | pub fn $field(&self) -> bool { 298 | if let $enum::$opt { $field, .. } = &self.style.ext { 299 | *$field 300 | } else { 301 | $default 302 | } 303 | } 304 | }; 305 | } 306 | 307 | impl<'a> DPIStyle<'a> { 308 | pub fn new_from(dpi_factor: f32, style: &'a Style) -> Self { 309 | Self { style, dpi_factor } 310 | } 311 | 312 | pub fn border_style(&self) -> BorderStyle { 313 | match self.style.border_style { 314 | BorderStyle::Rect => BorderStyle::Rect, 315 | BorderStyle::Hex { offset } => BorderStyle::Hex { offset: offset * self.dpi_factor }, 316 | BorderStyle::Bevel { corner_offsets } => BorderStyle::Bevel { 317 | corner_offsets: ( 318 | corner_offsets.0 * self.dpi_factor, 319 | corner_offsets.1 * self.dpi_factor, 320 | corner_offsets.2 * self.dpi_factor, 321 | corner_offsets.3 * self.dpi_factor, 322 | ), 323 | }, 324 | } 325 | } 326 | 327 | pub fn text_align(&self) -> Align { 328 | self.style.text_align 329 | } 330 | 331 | pub fn text_valign(&self) -> VAlign { 332 | self.style.text_valign 333 | } 334 | 335 | pub fn color_by_idx(&self, idx: usize) -> (f32, f32, f32) { 336 | self.style.color_by_idx(idx) 337 | } 338 | 339 | pub fn choose_shadow_color(&self, is_active: bool, is_hovered: bool) -> (f32, f32, f32) { 340 | self.style.choose_shadow_color(is_active, is_hovered) 341 | } 342 | 343 | pub fn choose_border_color(&self, is_active: bool, is_hovered: bool) -> (f32, f32, f32) { 344 | self.style.choose_border_color(is_active, is_hovered) 345 | } 346 | 347 | pub fn choose_color(&self, is_active: bool, is_hovered: bool) -> (f32, f32, f32) { 348 | self.style.choose_color(is_active, is_hovered) 349 | } 350 | 351 | pub fn shadow_offs(&self) -> (f32, f32) { 352 | (self.style.shadow_offs.0 * self.dpi_factor, self.style.shadow_offs.1 * self.dpi_factor) 353 | } 354 | 355 | pub fn apply_padding(&self, pos: Rect) -> Rect { 356 | self.style.apply_padding(self.dpi_factor, pos) 357 | } 358 | 359 | dpi_accessor! {border} 360 | dpi_accessor! {border2} 361 | dpi_accessor! {line} 362 | dpi_accessor! {pad_left} 363 | dpi_accessor! {pad_right} 364 | dpi_accessor! {pad_top} 365 | dpi_accessor! {pad_bottom} 366 | dpi_accessor! {pad_item} 367 | dpi_accessor! {font_size} 368 | 369 | dpi_ext_accessor! {StyleExt::Graph, graph_line, 0.9} 370 | dpi_ext_accessor! {StyleExt::Graph, vline1, 1.0} 371 | dpi_ext_accessor! {StyleExt::Graph, vline2, 1.0} 372 | dpi_ext_accessor! {StyleExt::Graph, hline, 0.0} 373 | 374 | dpi_ext_accessor! {StyleExt::PatternEditor, row_height, 14.0} 375 | dpi_ext_accessor! {StyleExt::PatternEditor, col_width, 38.0} 376 | dpi_ext_accessor! {StyleExt::PatternEditor, col_div_pad, 3.0} 377 | 378 | color_accessor! {bg_color} 379 | color_accessor! {color} 380 | color_accessor! {color2} 381 | color_accessor! {border_color} 382 | color_accessor! {border2_color} 383 | color_accessor! {shadow_color} 384 | color_accessor! {hover_shadow_color} 385 | color_accessor! {hover_border_color} 386 | color_accessor! {hover_color} 387 | color_accessor! {active_shadow_color} 388 | color_accessor! {active_border_color} 389 | color_accessor! {active_color} 390 | color_accessor! {inactive_color} 391 | color_accessor! {selected_color} 392 | 393 | color_ext_accessor! {StyleExt::Graph, hline_color, UI_ACCENT_CLR} 394 | color_ext_accessor! {StyleExt::Graph, vline1_color, UI_PRIM2_CLR} 395 | color_ext_accessor! {StyleExt::Graph, vline2_color, UI_PRIM_CLR} 396 | 397 | color_ext_accessor! {StyleExt::BlockCode, grid_marker_color, UI_ACCENT_DARK_CLR} 398 | color_ext_accessor! {StyleExt::BlockCode, block_bg_hover_color, UI_ACCENT_CLR} 399 | color_ext_accessor! {StyleExt::BlockCode, block_bg_color, UI_ACCENT_BG2_CLR} 400 | color_ext_accessor! {StyleExt::BlockCode, port_select_color, UI_SELECT_CLR} 401 | 402 | bool_ext_accessor! {StyleExt::BlockCode, with_markers, false} 403 | } 404 | -------------------------------------------------------------------------------- /src/widget.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Weird Constructor 2 | // This file is a part of HexoTK. Released under GPL-3.0-or-later. 3 | // See README.md and COPYING for details. 4 | 5 | use crate::painter::{ImgRef, LblDebugTag}; 6 | use crate::style::Style; 7 | use crate::{Control, EvPayload, Event, EventCore, Painter, PopupPos, Rect, UINotifierRef}; 8 | use std::cell::RefCell; 9 | use std::rc::{Rc, Weak}; 10 | 11 | use morphorm::{LayoutType, PositionType, Units}; 12 | 13 | thread_local! { 14 | pub static WIDGET_ID_COUNTER: RefCell = RefCell::new(1); 15 | } 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct Layout { 19 | pub visible: bool, 20 | 21 | pub layout_type: Option, 22 | pub position_type: Option, 23 | pub width: Option, 24 | pub height: Option, 25 | pub min_width: Option, 26 | pub min_height: Option, 27 | pub max_width: Option, 28 | pub max_height: Option, 29 | pub left: Option, 30 | pub right: Option, 31 | pub top: Option, 32 | pub bottom: Option, 33 | pub min_left: Option, 34 | pub max_left: Option, 35 | pub min_right: Option, 36 | pub max_right: Option, 37 | pub min_top: Option, 38 | pub max_top: Option, 39 | pub min_bottom: Option, 40 | pub max_bottom: Option, 41 | pub child_left: Option, 42 | pub child_right: Option, 43 | pub child_top: Option, 44 | pub child_bottom: Option, 45 | pub row_between: Option, 46 | pub col_between: Option, 47 | pub grid_rows: Option>, 48 | pub grid_cols: Option>, 49 | pub row_index: Option, 50 | pub col_index: Option, 51 | pub col_span: Option, 52 | pub row_span: Option, 53 | pub border_left: Option, 54 | pub border_right: Option, 55 | pub border_top: Option, 56 | pub border_bottom: Option, 57 | } 58 | 59 | impl Layout { 60 | pub fn new() -> Self { 61 | Self { 62 | visible: true, 63 | layout_type: None, 64 | position_type: None, 65 | width: None, 66 | height: None, 67 | min_width: None, 68 | min_height: None, 69 | max_width: None, 70 | max_height: None, 71 | left: None, 72 | right: None, 73 | top: None, 74 | bottom: None, 75 | min_left: None, 76 | max_left: None, 77 | min_right: None, 78 | max_right: None, 79 | min_top: None, 80 | max_top: None, 81 | min_bottom: None, 82 | max_bottom: None, 83 | child_left: None, 84 | child_right: None, 85 | child_top: None, 86 | child_bottom: None, 87 | row_between: None, 88 | col_between: None, 89 | grid_rows: None, 90 | grid_cols: None, 91 | row_index: None, 92 | col_index: None, 93 | col_span: None, 94 | row_span: None, 95 | border_left: None, 96 | border_right: None, 97 | border_top: None, 98 | border_bottom: None, 99 | } 100 | } 101 | } 102 | 103 | #[derive(Debug, Clone)] 104 | pub struct Widget(Rc>); 105 | 106 | impl Widget { 107 | pub fn new(style: Rc