From 856a634faa1c9013e6f33c87bfbb1e4e7524e997 Mon Sep 17 00:00:00 2001 From: T4r4sB Date: Wed, 15 Jun 2022 23:35:02 +0300 Subject: [PATCH] Implemented closing buttons on tab control, fixed some div-2-rounding bugs --- application/src/gui/gui_components.rs | 639 ++++++++++++++++++-------- application/src/gui/mod.rs | 60 ++- application/src/job_system.rs | 4 +- application/src/keys.rs | 9 + gui_test/src/bottom_panel.rs | 8 +- gui_test/src/draw_menu.rs | 4 +- gui_test/src/editor.rs | 137 ++++++ gui_test/src/file_menu.rs | 143 ++++-- gui_test/src/group_menu.rs | 4 +- gui_test/src/gui_helper.rs | 24 +- gui_test/src/main.rs | 182 ++------ gui_test/src/options_menu.rs | 83 ++-- gui_test/src/top_panel.rs | 16 +- gui_test/src/transform_menu.rs | 4 +- window/src/lib.rs | 14 +- 15 files changed, 861 insertions(+), 470 deletions(-) create mode 100644 gui_test/src/editor.rs diff --git a/application/src/gui/gui_components.rs b/application/src/gui/gui_components.rs index 4065311..7d875dd 100644 --- a/application/src/gui/gui_components.rs +++ b/application/src/gui/gui_components.rs @@ -1,6 +1,6 @@ use std::cell::RefCell; use std::cmp::{max, min}; -use std::ops::{Deref, DerefMut}; +use std::ops::DerefMut; use std::rc::Rc; use crate::callback; @@ -22,6 +22,7 @@ pub struct Container { base: GuiControlBase, layout: ContainerLayout, children: Vec>>, + empty_space_state: EmptySpaceState, support_compression: bool, } @@ -31,6 +32,7 @@ impl Container { base: GuiControlBase::new(size_constraints), layout, children: Default::default(), + empty_space_state: EmptySpaceState::Empty, support_compression: false, } } @@ -40,22 +42,36 @@ impl Container { self } - pub fn add_child(&mut self, control: Control) -> Rc> { + pub fn empty_space_state(mut self, empty_space_state: EmptySpaceState) -> Self { + self.empty_space_state = empty_space_state; + self + } + + pub fn child_count(&self) -> usize { + self.children.len() + } + + pub fn insert_child( + &mut self, + index: usize, + control: Control, + ) -> Rc> { let (untyped, typed) = GuiSystem::create_rc_by_control(control); untyped.borrow_mut().on_message(GuiMessage::Create); if self.base.visible { untyped.borrow_mut().on_message(GuiMessage::Show); } - self.children.push(untyped); + self.children.insert(index, untyped); typed } - pub fn delete_child(&mut self, control: &Rc>) { - let control_base = control.borrow_mut().get_base_mut() as *const _; - self.children.retain(|child| { - let child = child.borrow_mut().get_base_mut() as *const _; - control_base != child - }); + pub fn add_child(&mut self, control: Control) -> Rc> { + let index = self.child_count(); + self.insert_child(index, control) + } + + pub fn delete_child(&mut self, index: usize) { + self.children.remove(index); } } @@ -125,7 +141,7 @@ macro_rules! fill_empty_space { position_to_image_size($self.base.rect.relative(fill_rect.left_top)), position_to_image_size($self.base.rect.relative(fill_rect.right_bottom)), ); - GuiControlBase::erase_background(&mut buf_for_fill, $theme); + GuiSystem::erase_background(&mut buf_for_fill, $self.empty_space_state, $theme); } let mut fill_rect = $self.base.rect; @@ -134,7 +150,7 @@ macro_rules! fill_empty_space { position_to_image_size($self.base.rect.relative(fill_rect.left_top)), position_to_image_size($self.base.rect.relative(fill_rect.right_bottom)), ); - GuiControlBase::erase_background(&mut buf_for_fill, $theme); + GuiSystem::erase_background(&mut buf_for_fill, $self.empty_space_state, $theme); }; } @@ -156,38 +172,72 @@ impl GuiControl for Container { return true; } - GuiMessage::UpdateSizeConstraints(size_constraints) => { - if self.support_compression { - return true; + GuiMessage::UpdateSizeConstraints => { + for child in &self.children { + child + .borrow_mut() + .on_message(GuiMessage::UpdateSizeConstraints); } let child_real_constraints: Vec<_> = self .children .iter_mut() - .map(|c| GuiSystem::get_size_constraints(c.borrow_mut().deref_mut())) + .map(|c| c.borrow_mut().get_base_mut().current_size_constraints) .collect(); - let mut minimal_size: Position = (0, 0); + let child_minimal_sizes: Vec<_> = self + .children + .iter_mut() + .map(|c| c.borrow_mut().get_base_mut().minimal_size) + .collect(); + + let mut abs_constraints: Position = (0, 0); + let minimal_size = &mut self.base.minimal_size; + *minimal_size = (0, 0); match self.layout { ContainerLayout::Vertical => { for child_real_constraints in &child_real_constraints { - minimal_size.0 = max(minimal_size.0, child_real_constraints.0.absolute); - minimal_size.1 += child_real_constraints.1.absolute; + abs_constraints.0 = + max(abs_constraints.0, child_real_constraints.0.absolute); + abs_constraints.1 += child_real_constraints.1.absolute; + } + for child_minimal_sizes in &child_minimal_sizes { + minimal_size.0 = max(minimal_size.0, child_minimal_sizes.0); + if !self.support_compression { + minimal_size.1 += child_minimal_sizes.1; + } + } + minimal_size.0 = max(minimal_size.0, self.base.size_constraints.0.absolute); + if !self.support_compression { + minimal_size.1 = + max(minimal_size.1, self.base.size_constraints.1.absolute); } } ContainerLayout::Horizontal => { for child_real_constraints in &child_real_constraints { - minimal_size.0 += child_real_constraints.0.absolute; - minimal_size.1 = max(minimal_size.1, child_real_constraints.1.absolute); + abs_constraints.0 += child_real_constraints.0.absolute; + abs_constraints.1 = + max(abs_constraints.1, child_real_constraints.1.absolute); + } + for child_minimal_sizes in &child_minimal_sizes { + if !self.support_compression { + minimal_size.0 += child_minimal_sizes.0; + } + minimal_size.1 = max(minimal_size.1, child_minimal_sizes.1); } + if !self.support_compression { + minimal_size.0 = + max(minimal_size.0, self.base.size_constraints.0.absolute); + } + minimal_size.1 = max(minimal_size.1, self.base.size_constraints.1.absolute); } } self.base.current_size_constraints.0.absolute = - max(self.base.size_constraints.0.absolute, minimal_size.0); + max(self.base.size_constraints.0.absolute, abs_constraints.0); self.base.current_size_constraints.1.absolute = - max(self.base.size_constraints.1.absolute, minimal_size.1); - *size_constraints = self.base.current_size_constraints; + max(self.base.size_constraints.1.absolute, abs_constraints.1); + return true; } GuiMessage::RectUpdated => { @@ -271,46 +321,30 @@ impl GuiControl for Container { } #[derive(Debug)] -pub struct ColorBox { +pub struct EmptySpace { base: GuiControlBase, + state: EmptySpaceState, } -impl ColorBox { - pub fn new(size_constraints: SizeConstraints) -> Self { +impl EmptySpace { + pub fn new_empty(size_constraints: SizeConstraints) -> Self { Self { base: GuiControlBase::new(size_constraints), + state: EmptySpaceState::Empty, } } -} - -impl GuiControl for ColorBox { - fn get_base_mut(&mut self) -> &mut GuiControlBase { - &mut self.base - } - fn on_message(&mut self, m: GuiMessage) -> bool { - match m { - GuiMessage::Draw(buf, theme, force) => { - if self.base.can_draw(force) { - buf.fill(|d| *d = theme.splitter); - } - - return true; - } - _ => return false, + pub fn new_inactive(size_constraints: SizeConstraints) -> Self { + Self { + base: GuiControlBase::new(size_constraints), + state: EmptySpaceState::Inactive, } } -} - -#[derive(Debug)] -pub struct EmptySpace { - base: GuiControlBase, -} -impl EmptySpace { - pub fn new(size_constraints: SizeConstraints) -> Self { + pub fn new_splitter(size_constraints: SizeConstraints) -> Self { Self { base: GuiControlBase::new(size_constraints), + state: EmptySpaceState::Splitter, } } } @@ -324,8 +358,9 @@ impl GuiControl for EmptySpace { match m { GuiMessage::Draw(buf, theme, force) => { if self.base.can_draw(force) { - GuiControlBase::erase_background(buf, theme); + GuiSystem::erase_background(buf, self.state, theme); } + return true; } _ => return false, @@ -347,7 +382,7 @@ enum ButtonCheckState { None, CheckBox(bool), RadioButton(bool), - GroupButton(bool), + TabButton(bool), } #[derive(Debug)] @@ -423,8 +458,8 @@ impl Button { self } - pub fn group_button(self) -> Self { - *self.check_state.borrow_mut() = ButtonCheckState::GroupButton(false); + pub fn tab_button(self) -> Self { + *self.check_state.borrow_mut() = ButtonCheckState::TabButton(false); self } @@ -439,7 +474,7 @@ impl Button { ButtonCheckState::None => false, ButtonCheckState::CheckBox(_) => true, ButtonCheckState::RadioButton(_) => true, - ButtonCheckState::GroupButton(_) => false, + ButtonCheckState::TabButton(_) => false, } } @@ -448,7 +483,7 @@ impl Button { ButtonCheckState::None => false, ButtonCheckState::CheckBox(c) => c, ButtonCheckState::RadioButton(c) => c, - ButtonCheckState::GroupButton(c) => c, + ButtonCheckState::TabButton(c) => c, } } @@ -457,7 +492,7 @@ impl Button { ButtonCheckState::None => {} ButtonCheckState::CheckBox(c) => *c = checked, ButtonCheckState::RadioButton(c) => *c = checked, - ButtonCheckState::GroupButton(c) => *c = checked, + ButtonCheckState::TabButton(c) => *c = checked, } } @@ -466,7 +501,7 @@ impl Button { ButtonCheckState::None => "", ButtonCheckState::CheckBox(_) => "V", ButtonCheckState::RadioButton(_) => "●", - ButtonCheckState::GroupButton(_) => "", + ButtonCheckState::TabButton(_) => "", } } @@ -491,15 +526,26 @@ impl Button { "○" } } - ButtonCheckState::GroupButton(_) => "", + ButtonCheckState::TabButton(_) => "", } } pub fn get_background_color(&self, theme: &GuiColorTheme) -> u32 { - let color = if self.checked() { - avg_color(theme.background, theme.selected) - } else { - theme.background + let color = match *self.check_state.borrow() { + ButtonCheckState::TabButton(checked) => { + if checked { + theme.background + } else { + theme.inactive + } + } + _ => { + if self.checked() { + avg_color(theme.background, theme.selected) + } else { + theme.background + } + } }; if self.base.pressed { @@ -569,7 +615,7 @@ impl GuiControl for Button { let hotkey_size = self.font.get_size(&hotkey_text); let hotkey_width = min( - hotkey_size.0 + hotkey_size.1, + hotkey_size.0 + hotkey_size.1 / 2, buf.get_size().0 - check_width, ); @@ -579,12 +625,44 @@ impl GuiControl for Button { (buf.get_size().0 - hotkey_width, buf.get_size().1), ); let with_checks = self.has_checks(); + let text_size = self.font.get_size(&self.text); + let text_too_long = text_size.0 > caption_dst.get_size().0; + let caption_text = if text_too_long { + let get_longest_text = + |input: &str, font: &Font, max_size: usize| -> String { + let dots = "..."; + let mut good_text = String::new(); + let mut suffix = ""; + let mut size = font.get_size(dots).1 / 2; + let mut tmp = [0u8; 4]; + for (index, c) in input.chars().enumerate() { + size += font.get_size(c.encode_utf8(&mut tmp)).0; + if size > max_size && index > 0 { + return good_text + suffix; + } + good_text.push(c); + if index == 0 { + size += font.get_size(dots).0; + if size > max_size { + return good_text; + } + suffix = dots; + } + } + return good_text + suffix; + }; + + get_longest_text(&self.text, &self.font, caption_dst.get_size().0) + } else { + self.text.clone() + }; let mut caption_position = ( if with_checks { 0 } else { - (caption_dst.get_size().0 - check_width) as i32 / 2 + (caption_dst.get_size().0 - check_width + text_size.1 % 2) as i32 + / 2 }, caption_dst.get_size().1 as i32 / 2, ); @@ -598,20 +676,20 @@ impl GuiControl for Button { } else { self.font.layout_horizontal(TextLayoutHorizontal::MIDDLE) }; - font.color(theme.font) - .draw(&self.text, caption_position, &mut caption_dst); + font.color(theme.font).draw( + &caption_text, + caption_position, + &mut caption_dst, + ); // Draw hotkey let mut hotkey_text_dst = buf.window_mut((buf.get_size().0 - hotkey_width, 0), buf.get_size()); - let hotkey_position = ( - hotkey_text_dst.get_size().0 as i32 / 2, - hotkey_text_dst.get_size().1 as i32 / 2, - ); + let hotkey_position = (0, hotkey_text_dst.get_size().1 as i32 / 2); self.font - .layout_horizontal(TextLayoutHorizontal::MIDDLE) - .color(theme.hotkey) + .layout_horizontal(TextLayoutHorizontal::LEFT) + .color(theme.splitter) .draw(&hotkey_text, hotkey_position, &mut hotkey_text_dst); // Draw checkbox or radiobox @@ -702,7 +780,7 @@ impl GuiControl for TextBox { match m { GuiMessage::Draw(buf, theme, force) => { if self.base.can_draw(force) { - GuiControlBase::erase_background(buf, theme); + GuiSystem::erase_background(buf, EmptySpaceState::Empty, theme); let size = buf.get_size(); if size.0 > 0 && size.1 > 0 { let position = if self.padding { @@ -874,9 +952,20 @@ impl GuiControl for Edit { .fill(|p| *p = border_color); let mut inner = buf.window_mut((1, 1), (x - 1, y - 1)); - if self.base.focus { - inner.fill(|d| *d = avg_color(theme.background, theme.edit_focused)); + let color = if self.base.focus { + avg_color(theme.background, theme.edit_focused) + } else { + avg_color(theme.background, theme.inactive) + }; + + let color = if self.base.highlight { + avg_color(color, theme.highlight) + } else { + color + }; + inner.fill(|d| *d = color); + if self.base.focus { let text_before_cursor: String = self.text [self.scroll_position as usize..self.cursor_position as usize] .iter() @@ -887,10 +976,6 @@ impl GuiControl for Edit { .window_mut((cursor_position, 0), (cursor_position + 2, y - 2)) .fill(|p| *p = border_color); } - } else if self.base.highlight { - inner.fill(|d| *d = avg_color(theme.background, theme.edit_highlight)); - } else { - inner.fill(|d| *d = avg_color(theme.background, theme.inactive)); } let mut visible_text = String::new(); @@ -1332,36 +1417,75 @@ impl std::fmt::Debug for RadioGroupCallback { } } +#[derive(Debug)] +struct RadioGroupInternal { + buttons: Vec>>, + close_buttons: Vec>>, + selected_id: usize, + callback: Option, + selection_history: Vec, + ids: Vec, +} + #[derive(Debug)] pub struct RadioGroup { container: Container, - buttons: Rc>>>>, - selected_index: Rc>, - callback: Rc>>, + internal: Rc>, + tab: bool, + with_closes: bool, + last_id: usize, } impl RadioGroup { - pub fn new( + fn new_as( size_constraints: SizeConstraints, layout: ContainerLayout, caption: Option, + tab: bool, + with_closes: bool, ) -> Self { let mut container = Container::new(size_constraints, layout); + if tab { + container = container.empty_space_state(EmptySpaceState::Inactive); + } + caption.map(|caption| container.add_child(caption)); + Self { container, - buttons: Rc::new(RefCell::new(Vec::new())), - selected_index: Rc::new(RefCell::new(0)), - callback: Rc::new(RefCell::new(None)), + internal: Rc::new(RefCell::new(RadioGroupInternal { + buttons: Vec::new(), + close_buttons: Vec::new(), + selected_id: 0, + callback: None, + selection_history: Vec::new(), + ids: Vec::new(), + })), + tab, + with_closes, + last_id: 0, } } - pub fn get_index(&self) -> usize { - *self.selected_index.borrow() + pub fn new( + size_constraints: SizeConstraints, + layout: ContainerLayout, + caption: Option, + ) -> Self { + Self::new_as(size_constraints, layout, caption, false, false) + } + + pub fn compressed(mut self) -> Self { + self.container = self.container.compressed(); + self + } + + pub fn get_id(&self) -> usize { + self.internal.borrow().selected_id } pub fn set_callback(&mut self, callback: impl Fn(usize) + 'static) { - *self.callback.borrow_mut() = Some(RadioGroupCallback(Rc::new(callback))); + self.internal.borrow_mut().callback = Some(RadioGroupCallback(Rc::new(callback))); } pub fn callback(mut self, callback: impl Fn(usize) + 'static) -> Self { @@ -1369,57 +1493,173 @@ impl RadioGroup { self } - fn change_index_by( - buttons: &Weak>>>>, - selected_index: &Weak>, - callback: &Rc>>, - new_index: usize, - ) { - buttons.upgrade().map(|v| { - selected_index.upgrade().map(|i| { - v.borrow_mut().get(*i.borrow()).map(|b| { - b.borrow_mut().set_checked(false); - }); - - *i.borrow_mut() = new_index; - v.borrow_mut().get(*i.borrow()).map(|b| { - b.borrow_mut().set_checked(true); - }); - - if let Some(RadioGroupCallback(callback)) = callback.borrow().deref() { - callback(new_index); - } - }); - }); + fn find_index_by(ids: &[usize], id: usize) -> Option { + ids.iter().position(|&element| element == id) } - pub fn set_index(&self, new_index: usize) { - Self::change_index_by( - &Rc::downgrade(&self.buttons), - &Rc::downgrade(&self.selected_index), - &self.callback, - new_index, - ); + fn find_index(&self, id: usize) -> Option { + Self::find_index_by(&self.internal.borrow().ids, id) + } + + fn get_last_id(&self) -> usize { + self.last_id } - fn add_button_as(&mut self, button: Button, group: bool) { - let index_capture = Rc::downgrade(&self.selected_index); - let buttons_capture = Rc::downgrade(&self.buttons); - let index = self.buttons.borrow().len(); - let callback_capture = self.callback.clone(); - let button = if group { - button.group_button() + fn change_id_by(radio_group_internal: &mut RadioGroupInternal, new_id: usize) { + let buttons = &mut radio_group_internal.buttons; + let close_buttons = &mut radio_group_internal.close_buttons; + let set_state = |b: &Weak>, state: bool| { + b.upgrade().map(|b| b.borrow_mut().set_checked(state)); + }; + + if let Some(selection_position) = radio_group_internal + .selection_history + .iter() + .position(|&id| id == new_id) + { + radio_group_internal + .selection_history + .remove(selection_position); + } + + if let Some(old_index) = + Self::find_index_by(&radio_group_internal.ids, radio_group_internal.selected_id) + { + buttons.get(old_index).map(|b| set_state(b, false)); + close_buttons.get(old_index).map(|b| set_state(b, false)); + } + + if let Some(new_index) = Self::find_index_by(&radio_group_internal.ids, new_id) { + radio_group_internal.selected_id = new_id; + radio_group_internal.selection_history.push(new_id); + buttons.get(new_index).map(|b| set_state(b, true)); + close_buttons.get(new_index).map(|b| set_state(b, true)); + if let Some(RadioGroupCallback(callback)) = &radio_group_internal.callback { + callback(new_id); + } + } + } + + pub fn set_id(&self, new_id: usize) { + Self::change_id_by(self.internal.borrow_mut().deref_mut(), new_id); + } + + fn add_button_with_close_callback( + &mut self, + button: Button, + close_callback: impl Fn() + 'static, + ) { + let last_id = self.last_id; + let mut button = if self.tab { + button.tab_button() } else { button.radio_button() }; - let new_button = self.container.add_child(button.callback(move || { - Self::change_index_by(&buttons_capture, &index_capture, &callback_capture, index); - })); - self.buttons.borrow_mut().push(new_button.clone()); + + let button_font = button.font.clone(); + let close_text = "x"; + let close_text_size = button_font.get_size(close_text); + let close_button_size = (close_text_size.0 as i32 * 2, close_text_size.1 as i32 + 2); + let mut holder_constraints = button.base.size_constraints; + if self.with_closes { + holder_constraints.0.absolute += close_button_size.0; + } + + let new_button_holder = self.container.add_child(Container::new( + holder_constraints, + ContainerLayout::Horizontal, + )); + + button.base.set_size_constaints(SizeConstraints( + SizeConstraint::flexible(0), + SizeConstraint::flexible(0), + )); + + let internal = self.internal.clone(); + let new_button = + new_button_holder + .borrow_mut() + .add_child(button.callback(callback!( [internal] () { + Self::change_id_by( + internal.borrow_mut().deref_mut(), + last_id, + ); + }))); + + let first; + { + let mut internal = self.internal.borrow_mut(); + internal.buttons.push(Rc::downgrade(&new_button)); + if self.with_closes { + let close_button_holder = new_button_holder.borrow_mut().add_child( + Container::new( + SizeConstraints( + SizeConstraint::fixed(close_button_size.0 as i32), + SizeConstraint::fixed(close_button_size.1 as i32), + ), + ContainerLayout::Vertical, + ) + .empty_space_state(EmptySpaceState::Inactive), + ); + + let button = close_button_holder.borrow_mut().add_child( + Button::new( + SizeConstraints( + SizeConstraint::flexible(0), + SizeConstraint::fixed(holder_constraints.1.absolute * 75 / 100), + ), + close_text.to_string(), + button_font, + ) + .tab_button(), + ); + + button.borrow_mut().set_callback(close_callback); + internal.close_buttons.push(Rc::downgrade(&button)); + } + first = internal.ids.is_empty(); + internal.ids.push(last_id); + } + if first { + self.set_id(self.last_id); + } + self.last_id += 1; } pub fn add_button(&mut self, button: Button) { - self.add_button_as(button, false) + self.add_button_with_close_callback(button, || {}); + } + + pub fn delete_button(&mut self, id: usize) { + if let Some(index) = self.find_index(id) { + self.container.delete_child(index); + let prev_id; + { + let mut internal = self.internal.borrow_mut(); + if index < internal.buttons.len() { + internal.buttons.remove(index); + } + if index < internal.close_buttons.len() { + internal.close_buttons.remove(index); + } + if index < internal.ids.len() { + internal.ids.remove(index); + } + if Some(id) == internal.selection_history.last().map(|id| *id) { + internal.selection_history.pop(); + prev_id = internal + .selection_history + .last() + .or_else(|| internal.ids.get(0)) + .cloned(); + } else { + prev_id = None; + } + } + if let Some(prev_id) = prev_id { + self.set_id(prev_id); + } + } } } @@ -1443,28 +1683,39 @@ pub struct TabControl { } impl TabControl { - pub fn new(height: i32, font: Font) -> Self { + pub fn new(height: i32, font: Font, with_closes: bool) -> Self { let header_constrains = SizeConstraints(SizeConstraint::flexible(0), SizeConstraint::fixed(height)); - let full_constrains = SizeConstraints( - SizeConstraint::flexible(0), - SizeConstraint::fixed(height + 1), - ); + let full_constrains = + SizeConstraints(SizeConstraint::flexible(0), SizeConstraint::fixed(height)); Self { base: GuiControlBase::new(full_constrains), height, - header: RadioGroup::new(header_constrains, ContainerLayout::Horizontal, None), + header: RadioGroup::new_as( + header_constrains, + ContainerLayout::Horizontal, + None, + true, + with_closes, + ), children: vec![], font, } } - pub fn add_tab( + pub fn compressed(mut self) -> Self { + self.header = self.header.compressed(); + self + } + + pub fn add_tab_with_id( &mut self, caption: String, width: i32, control: Control, - ) -> Rc> { + close_callback: impl Fn(usize) + 'static, + ) -> (Rc>, usize) { + let tab_id = self.header.get_last_id(); let (untyped, typed) = GuiSystem::create_rc_by_control(control); let button = Button::new( SizeConstraints( @@ -1475,9 +1726,21 @@ impl TabControl { self.font.clone(), ); - self.header.add_button_as(button, true); + self.header + .add_button_with_close_callback(button, move || close_callback(tab_id)); + self.children.push(untyped); - typed + (typed, tab_id) + } + + pub fn add_tab( + &mut self, + caption: String, + width: i32, + control: Control, + ) -> Rc> { + self.add_tab_with_id(caption, width, control, |_: usize| {}) + .0 } fn hide_selected_tab(&self) { @@ -1496,19 +1759,27 @@ impl TabControl { } } - pub fn selected_tab_index(&self) -> usize { - self.header.get_index() + pub fn delete_tab(&mut self, id: usize) { + if let Some(index) = self.header.find_index(id) { + self.children.remove(index); + self.header.delete_button(id); + } + } + + pub fn selected_tab_id(&self) -> usize { + self.header.get_id() } pub fn get_selected_tab(&self) -> Option>> { - self.children - .get(self.selected_tab_index()) - .map(|t| t.clone()) + self.header + .find_index(self.selected_tab_id()) + .and_then(|index| self.children.get(index)) + .map(Rc::clone) } - pub fn select_tab(&mut self, index: usize) { + pub fn select_tab(&mut self, id: usize) { self.hide_selected_tab(); - self.header.set_index(index); + self.header.set_id(id); self.show_selected_tab(); } } @@ -1537,33 +1808,41 @@ impl GuiControl for TabControl { .header .on_message(GuiMessage::FindDestination(dest, position)); } - GuiMessage::UpdateSizeConstraints(size_constraints) => { + GuiMessage::UpdateSizeConstraints => { + self.header.on_message(GuiMessage::UpdateSizeConstraints); self.base.current_size_constraints = - GuiSystem::get_size_constraints(&mut self.header); - + self.header.get_base_mut().current_size_constraints; + self.base.minimal_size = self.header.get_base_mut().minimal_size; if let Some(selected_tab) = self.get_selected_tab() { - let tab_size_constraints = - GuiSystem::get_size_constraints(selected_tab.borrow_mut().deref_mut()); + selected_tab + .borrow_mut() + .on_message(GuiMessage::UpdateSizeConstraints); + let tab_size_constraints = selected_tab + .borrow_mut() + .get_base_mut() + .current_size_constraints; self.base.current_size_constraints.0.absolute = max( self.base.current_size_constraints.0.absolute, tab_size_constraints.0.absolute, ); self.base.current_size_constraints.1.absolute += - tab_size_constraints.1.absolute + 1; - } else { - self.base.current_size_constraints.1.absolute += 1; + tab_size_constraints.1.absolute; + self.base.current_size_constraints.1.relative = tab_size_constraints.1.relative; + + let tab_minimal_size = selected_tab.borrow_mut().get_base_mut().minimal_size; + self.base.minimal_size.0 = max(self.base.minimal_size.0, tab_minimal_size.0); + self.base.minimal_size.1 += tab_minimal_size.1; } - *size_constraints = self.base.current_size_constraints; return true; } GuiMessage::RectUpdated => { let mut header_rect = self.base.rect; - header_rect.right_bottom.1 = self.height; + header_rect.right_bottom.1 = header_rect.left_top.1 + self.height; GuiSystem::set_rect(&mut self.header, header_rect); if let Some(selected_tab) = self.get_selected_tab() { let mut selected_tab_rect = self.base.rect; - selected_tab_rect.left_top.1 = self.height + 1; + selected_tab_rect.left_top.1 = self.base.rect.left_top.1 + self.height; GuiSystem::set_rect(selected_tab.borrow_mut().deref_mut(), selected_tab_rect); } return true; @@ -1571,14 +1850,6 @@ impl GuiControl for TabControl { GuiMessage::Draw(buf, theme, force) => { if self.base.visible { let need_force = self.base.can_draw(force); - let has_button_updates = if let Some(button) = - self.header.buttons.borrow().get(self.selected_tab_index()) - { - button.borrow().base.need_redraw - } else { - false - }; - self.header .on_message(GuiMessage::Draw(buf, theme, need_force)); if let Some(selected_tab) = self.get_selected_tab() { @@ -1590,38 +1861,6 @@ impl GuiControl for TabControl { ); child.on_message(GuiMessage::Draw(&mut buf_for_child, theme, need_force)); } - if (need_force || has_button_updates) && self.height < buf.get_size().1 as i32 { - if let Some(button) = - self.header.buttons.borrow().get(self.selected_tab_index()) - { - let button = button.borrow_mut(); - let button_background_color = button.get_background_color(theme); - let button_rect = button.base.rect; - let x1 = button_rect.left_top.0 - self.base.rect.left_top.0; - let x2 = button_rect.right_bottom.0 - self.base.rect.left_top.0; - if x1 >= 0 && x1 <= x2 && x2 <= buf.get_size().0 as i32 { - let mut buf_for_fill = buf.window_mut( - position_to_image_size((x1, self.height)), - position_to_image_size((x2, self.height + 1)), - ); - buf_for_fill.fill(|p| *p = button_background_color); - - let mut buf_for_fill = buf.window_mut( - position_to_image_size((0, self.height)), - position_to_image_size((x1, self.height + 1)), - ); - buf_for_fill.fill(|p| *p = theme.splitter); - let mut buf_for_fill = buf.window_mut( - position_to_image_size((x2, self.height)), - position_to_image_size(( - buf.get_size().0 as i32, - self.height + 1, - )), - ); - buf_for_fill.fill(|p| *p = theme.splitter); - } - } - } } return true; @@ -1635,7 +1874,7 @@ impl GuiControl for TabControl { let active = active && self.base.visible && child.borrow_mut().get_base_mut().visible - && index == self.selected_tab_index(); + && index == self.selected_tab_id(); child .borrow_mut() .on_message(GuiMessage::GetHotkeys(hotkey_map, active)); diff --git a/application/src/gui/mod.rs b/application/src/gui/mod.rs index 4679338..5dcab37 100644 --- a/application/src/gui/mod.rs +++ b/application/src/gui/mod.rs @@ -68,6 +68,7 @@ impl std::fmt::Debug for HotkeyCallback { pub struct GuiControlBase { pub(crate) size_constraints: SizeConstraints, pub(crate) current_size_constraints: SizeConstraints, + pub(crate) minimal_size: Position, pub(crate) self_ref: Option>>, pub visible: bool, pub(crate) need_redraw: bool, @@ -82,6 +83,7 @@ impl GuiControlBase { let result = Self { size_constraints, current_size_constraints: size_constraints, + minimal_size: (size_constraints.0.absolute, size_constraints.1.absolute), self_ref: None, visible: true, need_redraw: false, @@ -100,14 +102,16 @@ impl GuiControlBase { result } - pub fn erase_background(buffer: &mut ImageViewMut, theme: &GuiColorTheme) { - buffer.fill(|p| *p = theme.background); + pub fn set_size_constaints(&mut self, constraints: SizeConstraints) { + self.size_constraints = constraints; + self.current_size_constraints = constraints; + self.minimal_size = (constraints.0.absolute, constraints.1.absolute); } } pub enum GuiMessage<'i, 'j> { Draw(&'i mut ImageViewMut<'j, u32>, &'i GuiColorTheme, bool), - UpdateSizeConstraints(&'i mut SizeConstraints), + UpdateSizeConstraints, FindDestination(&'i mut Rc>, Position), RectUpdated, FocusLose(JobSystem), @@ -139,39 +143,33 @@ pub(crate) fn avg_color(color1: u32, color2: u32) -> u32 { pub struct GuiColorTheme { pub background: u32, pub font: u32, - pub hotkey: u32, pub splitter: u32, pub highlight: u32, pub pressed: u32, pub selected: u32, pub inactive: u32, - pub edit_highlight: u32, pub edit_focused: u32, } pub static DARK_THEME: GuiColorTheme = GuiColorTheme { background: 0x000000, font: 0xCCCCCC, - hotkey: 0xAACCAA, splitter: 0xAACCAA, - highlight: 0xAAAAAA, + highlight: 0x9999CC, pressed: 0xFFFFFF, selected: 0x66CC66, - inactive: 0x444444, - edit_highlight: 0x666666, - edit_focused: 0x888888, + inactive: 0x555555, + edit_focused: 0x999999, }; pub static LIGHT_THEME: GuiColorTheme = GuiColorTheme { background: 0xFFFFFF, font: 0x000000, - hotkey: 0x664422, splitter: 0x664422, - highlight: 0x666666, + highlight: 0x779966, pressed: 0x222222, selected: 0xCC8844, inactive: 0xAAAAAA, - edit_highlight: 0xCCCCCC, edit_focused: 0xEEEEEE, }; @@ -231,6 +229,13 @@ macro_rules! set_property { }; } +#[derive(Debug, Copy, Clone)] +pub enum EmptySpaceState { + Empty, + Inactive, + Splitter, +} + impl GuiSystem { pub fn new(job_system: JobSystem) -> Self { Self { @@ -269,12 +274,6 @@ impl GuiSystem { result } - pub fn get_size_constraints(control: &mut dyn GuiControl) -> SizeConstraints { - let mut size_constraints = control.get_base_mut().size_constraints; - control.on_message(GuiMessage::UpdateSizeConstraints(&mut size_constraints)); - size_constraints - } - fn get_focus(&self) -> Option>> { self.focus.as_ref().and_then(Weak::upgrade).clone() } @@ -330,15 +329,32 @@ impl GuiSystem { self.color_theme = color_theme; } + pub fn get_color(state: EmptySpaceState, color_theme: &GuiColorTheme) -> u32 { + match state { + EmptySpaceState::Empty => color_theme.background, + EmptySpaceState::Inactive => color_theme.inactive, + EmptySpaceState::Splitter => color_theme.splitter, + } + } + + pub fn erase_background( + buffer: &mut ImageViewMut, + empty_space_state: EmptySpaceState, + theme: &GuiColorTheme, + ) { + let color = GuiSystem::get_color(empty_space_state, theme); + buffer.fill(|p| *p = color); + } + pub fn on_resize(&mut self) { self.updated = false; } - pub fn get_minimal_size(&self) -> Position { + pub fn get_minimal_size_of_system(&self) -> Position { if let Some(root) = &self.root { let mut root = root.borrow_mut(); - let size_constraints = Self::get_size_constraints(root.deref_mut()); - (size_constraints.0.absolute, size_constraints.1.absolute) + root.on_message(GuiMessage::UpdateSizeConstraints); + root.get_base_mut().minimal_size } else { (0, 0) } diff --git a/application/src/job_system.rs b/application/src/job_system.rs index 1a29137..10d8f63 100644 --- a/application/src/job_system.rs +++ b/application/src/job_system.rs @@ -17,10 +17,12 @@ impl JobSystem { self.jobs.borrow_mut().push(callback); } - pub fn run_all(&self) { + pub fn run_all(&self) -> bool { + let result = !self.jobs.borrow().is_empty(); for callback in self.jobs.borrow().iter() { callback(); } self.jobs.borrow_mut().clear(); + result } } diff --git a/application/src/keys.rs b/application/src/keys.rs index 4ff73f7..8a0ce35 100644 --- a/application/src/keys.rs +++ b/application/src/keys.rs @@ -239,6 +239,15 @@ impl Hotkey { } } + pub fn ctrl_shift(key: Key) -> Self { + Self { + key, + ctrl: true, + alt: false, + shift: true, + } + } + pub fn no_modifiers(&self) -> bool { !(self.ctrl || self.alt || self.shift) } diff --git a/gui_test/src/bottom_panel.rs b/gui_test/src/bottom_panel.rs index 1dc7b08..f4dbf96 100644 --- a/gui_test/src/bottom_panel.rs +++ b/gui_test/src/bottom_panel.rs @@ -36,7 +36,7 @@ pub fn create_bottom_panel( let _hr = bottom_panel .borrow_mut() - .add_child(ColorBox::new(SizeConstraints( + .add_child(EmptySpace::new_splitter(SizeConstraints( SizeConstraint::fixed(1), SizeConstraint::flexible(0), ))); @@ -55,7 +55,7 @@ pub fn create_bottom_panel( let _es = bottom_panel .borrow_mut() - .add_child(EmptySpace::new(SizeConstraints( + .add_child(EmptySpace::new_empty(SizeConstraints( SizeConstraint::fixed(font_symbol_size.0 as i32 / 2), SizeConstraint::flexible(0), ))); @@ -70,7 +70,7 @@ pub fn create_bottom_panel( let _es = bottom_panel .borrow_mut() - .add_child(EmptySpace::new(SizeConstraints( + .add_child(EmptySpace::new_empty(SizeConstraints( SizeConstraint::fixed(font_symbol_size.0 as i32 / 2), SizeConstraint::flexible(0), ))); @@ -85,7 +85,7 @@ pub fn create_bottom_panel( ); let _es = bottom_panel .borrow_mut() - .add_child(EmptySpace::new(SizeConstraints( + .add_child(EmptySpace::new_empty(SizeConstraints( SizeConstraint::fixed(font_symbol_size.0 as i32 / 2), SizeConstraint::flexible(0), ))); diff --git a/gui_test/src/draw_menu.rs b/gui_test/src/draw_menu.rs index 7658034..16bf622 100644 --- a/gui_test/src/draw_menu.rs +++ b/gui_test/src/draw_menu.rs @@ -12,7 +12,9 @@ pub fn create_draw_menu(parent: &mut TabControl, font: &Font) -> Rc &'static CadColorTheme { + match config.color_theme { + ColorTheme::Dark => &CAD_DARK_THEME, + ColorTheme::Beige => &CAD_BEIGE_THEME, + ColorTheme::Light => &CAD_LIGHT_THEME, + } +} + +pub fn get_gui_color_theme(config: &Config) -> &'static GuiColorTheme { + match config.color_theme { + ColorTheme::Dark => &DARK_THEME, + ColorTheme::Beige => &BEIGE_THEME, + ColorTheme::Light => &LIGHT_THEME, + } +} + +pub struct Editor { + pub last_file_id: usize, + pub config: Rc>, +} + +impl Editor { + pub fn new(config: Config) -> Self { + Self { + last_file_id: 0, + config: Rc::new(RefCell::new(config)), + } + } + + pub fn get_next_id(&mut self) -> usize { + self.last_file_id += 1; + self.last_file_id + } +} + +pub struct CadView { + base: GuiControlBase, + editor: Rc>, +} + +impl std::fmt::Debug for CadView { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.base.fmt(f) + } +} + +impl CadView { + pub fn new(size_constraints: SizeConstraints, editor: Rc>) -> Self { + Self { + base: GuiControlBase::new(size_constraints), + editor, + } + } +} + +impl GuiControl for CadView { + fn get_base_mut(&mut self) -> &mut GuiControlBase { + &mut self.base + } + + fn on_message(&mut self, m: GuiMessage) -> bool { + match m { + GuiMessage::Draw(buf, theme, force) => { + if self.base.can_draw(force) { + GuiSystem::erase_background(buf, EmptySpaceState::Empty, theme); + let curve = Curve::::circle(Point::new(100.0, 100.0), 50.0); + let e = Entity::Curve(curve); + let mut buffer = vec![(0, 0); buf.get_size().1 * 4]; + let cad_color_theme = + get_cad_color_theme(&self.editor.borrow().config.borrow()); + + match self.editor.borrow().config.borrow().curves_aa_mode { + CurvesAAMode::NoAntiAliasing => { + draw_curve(buf, &e, cad_color_theme.line_color, 1.0, &mut buffer, 1) + } + CurvesAAMode::AntiAliasingX2 => { + draw_curve(buf, &e, cad_color_theme.line_aa_color, 1.0, &mut buffer, 2) + } + CurvesAAMode::AntiAliasingX4 => { + draw_curve(buf, &e, cad_color_theme.line_aa_color, 1.0, &mut buffer, 4) + } + }; + } + + return true; + } + GuiMessage::MouseDown(_) => { + return true; + } + GuiMessage::MouseUp(_, _) => { + return true; + } + _ => return false, + } + } +} diff --git a/gui_test/src/file_menu.rs b/gui_test/src/file_menu.rs index ca6cffb..ec6a8fc 100644 --- a/gui_test/src/file_menu.rs +++ b/gui_test/src/file_menu.rs @@ -1,61 +1,102 @@ use std::cell::RefCell; use std::rc::Rc; +use application::callback; +use application::callback_body; use application::font::*; use application::gui::gui_components::*; use application::gui::*; +use application::keys::*; +use crate::editor::*; use crate::gui_helper::*; -pub fn create_file_menu(parent: &mut TabControl, font: &Font) -> Rc> { - let font_height = font.get_size("8").1 as i32 + 2; +pub fn create_file_menu( + parent: &mut TabControl, + font: &Font, + editor: Rc>, + draws: Rc>, +) -> Rc> { let menu_caption = "Файл"; let file_menu = parent.add_tab( menu_caption.to_string(), - font.get_size(menu_caption).0 as i32 + font_height, + GuiSystem::default_size(&menu_caption, None, &font) + .0 + .absolute, Container::new( - SizeConstraints(SizeConstraint::flexible(0), SizeConstraint::fixed(0)), - ContainerLayout::Vertical, + SizeConstraints(SizeConstraint::fixed(0), SizeConstraint::fixed(0)), + ContainerLayout::Horizontal, ), ); let default_panel = file_menu.borrow_mut().add_child(Container::new( - SizeConstraints(SizeConstraint::flexible(0), SizeConstraint::fixed(0)), - ContainerLayout::Horizontal, + SizeConstraints(SizeConstraint::fixed(0), SizeConstraint::fixed(0)), + ContainerLayout::Vertical, )); - let _new_button = default_panel.borrow_mut().add_child( - create_default_size_button("Новый", font.clone()).callback(|| { - panic!("test panic"); - }), - ); + { + let font = font.clone(); + let _new_button = default_panel.borrow_mut().add_child( + create_default_size_button_with_hotkey( + "Новый", + font.clone(), + Hotkey::ctrl(Key::N), + true, + ) + .callback(callback!([editor,draws]() { + new_file(&font,editor, draws ); + })), + ); + } - let _open_button = default_panel - .borrow_mut() - .add_child(create_default_size_button("Открыть", font.clone())); + let _open_button = + default_panel + .borrow_mut() + .add_child(create_default_size_button_with_hotkey( + "Открыть", + font.clone(), + Hotkey::ctrl(Key::O), + true, + )); - let _save_button = default_panel - .borrow_mut() - .add_child(create_default_size_button("Сохранить", font.clone())); + let _save_button = + default_panel + .borrow_mut() + .add_child(create_default_size_button_with_hotkey( + "Сохранить", + font.clone(), + Hotkey::ctrl(Key::S), + true, + )); - let _save_as_button = default_panel - .borrow_mut() - .add_child(create_default_size_button("Сохранить как", font.clone())); + let _save_as_button = + default_panel + .borrow_mut() + .add_child(create_default_size_button_with_hotkey( + "Сохранить как", + font.clone(), + Hotkey::ctrl_shift(Key::S), + true, + )); - let _close_button = default_panel - .borrow_mut() - .add_child(create_default_size_button("Закрыть", font.clone())); + let close_button = + default_panel + .borrow_mut() + .add_child(create_default_size_button_with_hotkey( + "Закрыть", + font.clone(), + Hotkey::alt(Key::F3), + true, + )); - let _es = file_menu - .borrow_mut() - .add_child(EmptySpace::new(SizeConstraints( - SizeConstraint::flexible(0), - SizeConstraint::fixed(font_height / 4), - ))); + close_button.borrow_mut().set_callback(callback!([draws]() { + let id = draws.borrow().selected_tab_id(); + draws.borrow_mut().delete_tab(id); + })); let dxf_panel = file_menu.borrow_mut().add_child(Container::new( SizeConstraints(SizeConstraint::flexible(0), SizeConstraint::fixed(0)), - ContainerLayout::Horizontal, + ContainerLayout::Vertical, )); let _import_button = dxf_panel @@ -73,3 +114,43 @@ pub fn create_file_menu(parent: &mut TabControl, font: &Font) -> Rc>, draws: Rc>) { + let id = editor.borrow_mut().get_next_id(); + let new_file_caption = format!("Новый чертёж {}", id); + let font_height = font.get_size("8").1 as i32 + 2; + let mut tab_content = Container::new( + SizeConstraints(SizeConstraint::flexible(0), SizeConstraint::flexible(0)), + ContainerLayout::Vertical, + ); + + tab_content.add_child(TextBox::new( + SizeConstraints( + SizeConstraint::flexible(0), + SizeConstraint::fixed(font_height), + ), + new_file_caption.clone(), + font.clone(), + )); + + tab_content.add_child(EmptySpace::new_splitter(SizeConstraints( + SizeConstraint::flexible(0), + SizeConstraint::fixed(1), + ))); + + tab_content.add_child(CadView::new( + SizeConstraints(SizeConstraint::flexible(200), SizeConstraint::flexible(200)), + editor.clone(), + )); + + let (_cad_tab, _document_id) = draws.borrow_mut().add_tab_with_id( + new_file_caption.clone(), + GuiSystem::default_size(&new_file_caption, None, &font) + .0 + .absolute, + tab_content, + callback!( [draws] (id) { + draws.borrow_mut().delete_tab(id); + }), + ); +} diff --git a/gui_test/src/group_menu.rs b/gui_test/src/group_menu.rs index d84b538..5a94376 100644 --- a/gui_test/src/group_menu.rs +++ b/gui_test/src/group_menu.rs @@ -12,7 +12,9 @@ pub fn create_group_menu(parent: &mut TabControl, font: &Font) -> Rc Button { ) } +pub fn create_default_size_button_with_hotkey( + text: &str, + font: Font, + hotkey: Hotkey, + global: bool, +) -> Button { + Button::new( + Button::default_size(text, Some(hotkey), &font), + text.to_string(), + font, + ) + .hotkey(hotkey, global) +} + pub fn create_default_size_check_button(text: &str, font: Font) -> Button { Button::new( Button::default_checkbox_size(text, None, &font), @@ -20,18 +34,12 @@ pub fn create_default_size_check_button(text: &str, font: Font) -> Button { ) } -pub fn create_default_size_check_button_with_hotkey( - text: &str, - hotkey: Hotkey, - global: bool, - font: Font, -) -> Button { +pub fn create_default_size_radio_button(text: &str, font: Font) -> Button { Button::new( - Button::default_checkbox_size(text, Some(hotkey), &font), + Button::default_radiobutton_size(text, None, &font), text.to_string(), font, ) - .hotkey(hotkey, global) } pub fn create_default_size_edit(text: &str, font: Font, clipboard: Clipboard) -> Edit { diff --git a/gui_test/src/main.rs b/gui_test/src/main.rs index f483482..80f363e 100644 --- a/gui_test/src/main.rs +++ b/gui_test/src/main.rs @@ -5,19 +5,18 @@ use application::draw_context::*; use application::gui::gui_components::*; use application::gui::*; -use curves::curves::*; -use curves::points::*; -use curves::render::*; - use window::*; use bottom_panel::*; use config::*; +use editor::*; +use file_menu::*; use top_panel::*; mod bottom_panel; mod config; mod draw_menu; +mod editor; mod file_menu; mod group_menu; mod gui_helper; @@ -25,122 +24,23 @@ mod options_menu; mod top_panel; mod transform_menu; -struct CadColorTheme { - line_color: u32, - line_aa_color: u32, -} - -static CAD_DARK_THEME: CadColorTheme = CadColorTheme { - line_color: 0x88AA88, - line_aa_color: 0xCCFFCC, -}; - -static CAD_BEIGE_THEME: CadColorTheme = CadColorTheme { - line_color: 0x442200, - line_aa_color: 0x442200, -}; - -static CAD_LIGHT_THEME: CadColorTheme = CadColorTheme { - line_color: 0x000000, - line_aa_color: 0x000000, -}; - -static BEIGE_THEME: GuiColorTheme = GuiColorTheme { - background: 0xDDCCAA, - font: 0x000000, - hotkey: 0x224466, - splitter: 0x224466, - highlight: 0x666666, - pressed: 0x222222, - selected: 0x4499CC, - inactive: 0xAAAAAA, - edit_highlight: 0xCCCCCC, - edit_focused: 0xEEEEEE, -}; - -fn get_cad_color_theme(config: &Config) -> &'static CadColorTheme { - match config.color_theme { - ColorTheme::Dark => &CAD_DARK_THEME, - ColorTheme::Beige => &CAD_BEIGE_THEME, - ColorTheme::Light => &CAD_LIGHT_THEME, - } -} - -fn get_gui_color_theme(config: &Config) -> &'static GuiColorTheme { - match config.color_theme { - ColorTheme::Dark => &DARK_THEME, - ColorTheme::Beige => &BEIGE_THEME, - ColorTheme::Light => &LIGHT_THEME, - } -} - -#[derive(Debug)] -pub struct CadView { - base: GuiControlBase, - config: Rc>, -} - -impl CadView { - pub fn new(size_constraints: SizeConstraints, config: Rc>) -> Self { - Self { - base: GuiControlBase::new(size_constraints), - config, - } - } -} - -impl GuiControl for CadView { - fn get_base_mut(&mut self) -> &mut GuiControlBase { - &mut self.base - } - - fn on_message(&mut self, m: GuiMessage) -> bool { - match m { - GuiMessage::Draw(buf, theme, force) => { - if self.base.can_draw(force) { - GuiControlBase::erase_background(buf, theme); - let curve = Curve::::circle(Point::new(100.0, 100.0), 50.0); - let e = Entity::Curve(curve); - let mut buffer = vec![(0, 0); buf.get_size().1 * 4]; - let cad_color_theme = get_cad_color_theme(&self.config.borrow()); - - match self.config.borrow().curves_aa_mode { - CurvesAAMode::NoAntiAliasing => { - draw_curve(buf, &e, cad_color_theme.line_color, 1.0, &mut buffer, 1) - } - CurvesAAMode::AntiAliasingX2 => { - draw_curve(buf, &e, cad_color_theme.line_aa_color, 1.0, &mut buffer, 2) - } - CurvesAAMode::AntiAliasingX4 => { - draw_curve(buf, &e, cad_color_theme.line_aa_color, 1.0, &mut buffer, 4) - } - }; - } - - return true; - } - GuiMessage::MouseDown(_) => { - return true; - } - _ => return false, - } - } -} - struct GuiTest { - config: Rc>, + editor: Rc>, } impl GuiTest { - fn new(config: Rc>) -> Self { - Self { config } + fn new(config: Config) -> Self { + Self { + editor: Rc::new(RefCell::new(Editor::new(config))), + } } fn rebuild_gui( - config: Rc>, + editor: Rc>, context: Rc>, top_panel_index: usize, ) { + let config = editor.borrow().config.clone(); let font_size = config.borrow().font_size.0; let font_aa_mode = config.borrow().font_aa_mode; @@ -149,32 +49,43 @@ impl GuiTest { .borrow_mut() .font_factory .new_font("MS Sans Serif", font_size, font_aa_mode); + let font_height = default_font.get_size("8").1 as i32 + 2; + let root = context.borrow_mut().gui_system.set_root(Container::new( SizeConstraints(SizeConstraint::flexible(0), SizeConstraint::flexible(0)), ContainerLayout::Vertical, )); + let _hr = root + .borrow_mut() + .add_child(EmptySpace::new_splitter(SizeConstraints( + SizeConstraint::flexible(0), + SizeConstraint::fixed(1), + ))); + + let middle = root + .borrow_mut() + .add_child(TabControl::new(font_height, default_font.clone(), true).compressed()); + + for _ in 1..7 { + new_file(&default_font, editor.clone(), middle.clone()); + } + create_top_panel( &mut root.borrow_mut(), &default_font, - config.clone(), + editor.clone(), + middle.clone(), context.clone(), top_panel_index, ); - let _hr = root.borrow_mut().add_child(ColorBox::new(SizeConstraints( - SizeConstraint::flexible(0), - SizeConstraint::fixed(1), - ))); - - let _middle = root.borrow_mut().add_child(CadView::new( - SizeConstraints(SizeConstraint::flexible(100), SizeConstraint::flexible(100)), - config.clone(), - )); - let _hr = root.borrow_mut().add_child(ColorBox::new(SizeConstraints( - SizeConstraint::flexible(0), - SizeConstraint::fixed(1), - ))); + let _hr = root + .borrow_mut() + .add_child(EmptySpace::new_splitter(SizeConstraints( + SizeConstraint::flexible(0), + SizeConstraint::fixed(1), + ))); context .borrow_mut() @@ -186,22 +97,25 @@ impl GuiTest { impl window::Application for GuiTest { fn on_create(&mut self, context: Rc>) { - Self::rebuild_gui(self.config.clone(), context.clone(), DRAW_MENU_INDEX); + Self::rebuild_gui(self.editor.clone(), context.clone(), DRAW_MENU_INDEX); } fn on_close(&mut self, _context: Rc>) { - save_config(&self.config.borrow()); + save_config(&self.editor.borrow().config.borrow()); } fn on_change_position(&mut self, window_position: WindowPosition) { + let config = self.editor.borrow_mut().config.clone(); + let config = &mut config.borrow_mut(); + let mut config_window_position = &mut config.window_position; if window_position.maximized { - if let Some(window_position) = &mut self.config.borrow_mut().window_position { - window_position.maximized = true; + if let Some(config_window_position) = &mut config_window_position { + config_window_position.maximized = true; } else { - self.config.borrow_mut().window_position = Some(window_position); + *config_window_position = Some(window_position); } } else { - self.config.borrow_mut().window_position = Some(window_position); + *config_window_position = Some(window_position); } } @@ -211,11 +125,9 @@ impl window::Application for GuiTest { fn main() { let config = load_config().unwrap_or_default(); let window_position = config.window_position; - if let Err(_) = window::run_application( - "ОтКАД", - Box::new(GuiTest::new(Rc::new(RefCell::new(config)))), - window_position, - ) { + if let Err(_) = + window::run_application("ОтКАД", Box::new(GuiTest::new(config)), window_position) + { // Do nothing, read message and exit } } diff --git a/gui_test/src/options_menu.rs b/gui_test/src/options_menu.rs index 29449bc..9e6c2b0 100644 --- a/gui_test/src/options_menu.rs +++ b/gui_test/src/options_menu.rs @@ -6,11 +6,11 @@ use application::callback_body; use application::font::*; use application::gui::gui_components::*; use application::gui::*; -use application::keys::*; use window::show_message; use crate::config::*; +use crate::editor::*; use crate::gui_helper::*; use crate::GuiTest; use crate::OPTIONS_MENU_INDEX; @@ -18,14 +18,17 @@ use crate::OPTIONS_MENU_INDEX; pub fn create_options_menu( parent: &mut TabControl, font: &Font, - config: Rc>, + editor: Rc>, context: Rc>, ) -> Rc> { + let config = editor.borrow().config.clone(); let font_height = font.get_size("8").1 as i32 + 2; let menu_caption = "Опции"; let options_menu = parent.add_tab( menu_caption.to_string(), - font.get_size(menu_caption).0 as i32 + font_height, + GuiSystem::default_size(&menu_caption, None, &font) + .0 + .absolute, Container::new( SizeConstraints(SizeConstraint::flexible(0), SizeConstraint::fixed(0)), ContainerLayout::Vertical, @@ -66,7 +69,8 @@ pub fn create_options_menu( )); font_size_input.borrow_mut().set_enter_callback(callback!( - [font_size_input, config, context] (text) { + [font_size_input, editor, context] (text) { + let config = editor.borrow().config.clone(); let old_font_size = config.borrow().font_size; match text.parse::() { Ok(new_font_size) => { @@ -85,7 +89,7 @@ pub fn create_options_menu( let new_font_size = config.borrow().font_size; if old_font_size != new_font_size { - GuiTest::rebuild_gui(config.clone(), context.clone(), OPTIONS_MENU_INDEX); + GuiTest::rebuild_gui(editor.clone(), context.clone(), OPTIONS_MENU_INDEX); } } )); @@ -104,23 +108,23 @@ pub fn create_options_menu( let _no_font_anti_alisaing_button = font_anti_aliasing_selector .borrow_mut() - .add_button(create_default_size_check_button("Нету", font.clone())); + .add_button(create_default_size_radio_button("Нету", font.clone())); let _font_anti_alisaing_button = font_anti_aliasing_selector .borrow_mut() - .add_button(create_default_size_check_button("Пиксельное", font.clone())); + .add_button(create_default_size_radio_button("Пиксельное", font.clone())); let _font_true_type_button = font_anti_aliasing_selector .borrow_mut() - .add_button(create_default_size_check_button( + .add_button(create_default_size_radio_button( "Субпиксельное (true type)", font.clone(), )); font_anti_aliasing_selector .borrow_mut() - .set_index(match config.borrow().font_aa_mode { + .set_id(match config.borrow().font_aa_mode { FontAntiAliasingMode::NoAA => 0, FontAntiAliasingMode::AA => 1, FontAntiAliasingMode::TT => 2, @@ -137,7 +141,7 @@ pub fn create_options_menu( } let new_aa_index = config.borrow().font_aa_mode; if new_aa_index != old_aa_index { - GuiTest::rebuild_gui(config.clone(), context.clone(), OPTIONS_MENU_INDEX); + GuiTest::rebuild_gui(editor.clone(), context.clone(), OPTIONS_MENU_INDEX); } })); @@ -156,19 +160,19 @@ pub fn create_options_menu( let _no_line_anti_aliasing_button = line_anti_aliasing_selector .borrow_mut() - .add_button(create_default_size_check_button("Нету", font.clone())); + .add_button(create_default_size_radio_button("Нету", font.clone())); let _x2_line_anti_aliasing_button = line_anti_aliasing_selector .borrow_mut() - .add_button(create_default_size_check_button("Среднее", font.clone())); + .add_button(create_default_size_radio_button("Среднее", font.clone())); let _x4_line_anti_aliasing_button = line_anti_aliasing_selector .borrow_mut() - .add_button(create_default_size_check_button("Высшее", font.clone())); + .add_button(create_default_size_radio_button("Высшее", font.clone())); line_anti_aliasing_selector .borrow_mut() - .set_index(match config.borrow().curves_aa_mode { + .set_id(match config.borrow().curves_aa_mode { CurvesAAMode::NoAntiAliasing => 0, CurvesAAMode::AntiAliasingX2 => 1, CurvesAAMode::AntiAliasingX4 => 2, @@ -193,42 +197,21 @@ pub fn create_options_menu( Some(create_default_size_text_box("Цветовая тема:", font.clone())), )); - let dark_theme_hotkey = Hotkey::ctrl(Key::D); - let _dark_theme_button = - theme_selector - .borrow_mut() - .add_button(create_default_size_check_button_with_hotkey( - "Тёмная", - dark_theme_hotkey, - false, - font.clone(), - )); + let _dark_theme_button = theme_selector + .borrow_mut() + .add_button(create_default_size_radio_button("Тёмная", font.clone())); - let beige_theme_hotkey = Hotkey::new(Key::B); - let _beige_theme_button = - theme_selector - .borrow_mut() - .add_button(create_default_size_check_button_with_hotkey( - "Бежевая", - beige_theme_hotkey, - false, - font.clone(), - )); + let _beige_theme_button = theme_selector + .borrow_mut() + .add_button(create_default_size_radio_button("Бежевая", font.clone())); - let light_theme_hotkey = Hotkey::shift(Key::L); - let _light_theme_button = - theme_selector - .borrow_mut() - .add_button(create_default_size_check_button_with_hotkey( - "Светлая", - light_theme_hotkey, - true, - font.clone(), - )); + let _light_theme_button = theme_selector + .borrow_mut() + .add_button(create_default_size_radio_button("Светлая", font.clone())); theme_selector .borrow_mut() - .set_index(match config.borrow().color_theme { + .set_id(match config.borrow().color_theme { ColorTheme::Dark => 0, ColorTheme::Beige => 1, ColorTheme::Light => 2, @@ -243,11 +226,11 @@ pub fn create_options_menu( _ => {} } - context - .borrow_mut() - .gui_system - .set_color_theme(*crate::get_gui_color_theme(&config.borrow())); - } + context + .borrow_mut() + .gui_system + .set_color_theme(*get_gui_color_theme(&config.borrow())); + } )); options_menu diff --git a/gui_test/src/top_panel.rs b/gui_test/src/top_panel.rs index cd9f7c8..b319a96 100644 --- a/gui_test/src/top_panel.rs +++ b/gui_test/src/top_panel.rs @@ -4,8 +4,8 @@ use std::rc::Rc; use application::font::*; use application::gui::gui_components::*; -use crate::config::*; use crate::draw_menu::*; +use crate::editor::*; use crate::file_menu::*; use crate::group_menu::*; use crate::options_menu::*; @@ -17,23 +17,19 @@ pub static OPTIONS_MENU_INDEX: usize = 4; pub fn create_top_panel( root: &mut Container, font: &Font, - config: Rc>, + editor: Rc>, + draws: Rc>, context: Rc>, top_panel_index: usize, ) -> Rc> { let font_height = font.get_size("8").1 as i32 + 2; - let top_panel = root.add_child(TabControl::new(font_height, font.clone())); + let top_panel = root.insert_child(0, TabControl::new(font_height, font.clone(), false)); - create_file_menu(&mut top_panel.borrow_mut(), font); + create_file_menu(&mut top_panel.borrow_mut(), font, editor.clone(), draws); create_draw_menu(&mut top_panel.borrow_mut(), font); create_group_menu(&mut top_panel.borrow_mut(), font); create_transform_menu(&mut top_panel.borrow_mut(), font); - create_options_menu( - &mut top_panel.borrow_mut(), - font, - config.clone(), - context.clone(), - ); + create_options_menu(&mut top_panel.borrow_mut(), font, editor, context); top_panel.borrow_mut().select_tab(top_panel_index); top_panel diff --git a/gui_test/src/transform_menu.rs b/gui_test/src/transform_menu.rs index b2c90d2..a26cff8 100644 --- a/gui_test/src/transform_menu.rs +++ b/gui_test/src/transform_menu.rs @@ -13,7 +13,9 @@ pub fn create_transform_menu(parent: &mut TabControl, font: &Font) -> Rc Option { } fn adjust_window_size(context: Rc>, hwnd: HWND) -> APIResult<()> { - let minimal_size = context.borrow().gui_system.get_minimal_size(); + let minimal_size = context.borrow().gui_system.get_minimal_size_of_system(); let client_rect = get_client_rect(hwnd)?; if client_rect.right - client_rect.left >= minimal_size.0 && client_rect.bottom - client_rect.top >= minimal_size.1 @@ -268,8 +268,8 @@ fn adjust_window_size(context: Rc>, hwnd: HWND) -> APIResult<() let mut window_rect = get_window_rect(hwnd)?; window_rect.left -= dx / 2; window_rect.top -= dy / 2; - window_rect.right += dx / 2; - window_rect.bottom += dy / 2; + window_rect.right += (dx + 1) / 2; + window_rect.bottom += (dy + 1) / 2; adjust_rect(get_window_move_bounds(hwnd)?, &mut window_rect); unsafe { @@ -292,8 +292,10 @@ fn run_jobs(context: Rc>, hwnd: HWND) -> APIResult<()> { // attempt to recursion, wait for jobs } else { let job_system = context.borrow_mut().job_system.clone(); - job_system.run_all(); // jobs can borrow context - adjust_window_size(context, hwnd)?; + if job_system.run_all() { + // jobs can borrow context + adjust_window_size(context, hwnd)?; + } } Ok(()) } @@ -450,7 +452,7 @@ unsafe fn maybe_window_proc( let window_rect = get_window_rect(hwnd)?; let (_, _, context) = get_context()?; - let minimal_size = context.borrow().gui_system.get_minimal_size(); + let minimal_size = context.borrow().gui_system.get_minimal_size_of_system(); // Count size of bevel... let minimal_size_x = minimal_size.0 as i32 + (window_rect.right - window_rect.left) - (client_rect.right - client_rect.left);