From a80b3d010745c20d5adb8245e8e526e093390d56 Mon Sep 17 00:00:00 2001 From: Adrian Wozniak Date: Sun, 22 Dec 2019 14:15:47 +0100 Subject: [PATCH] Simplify components --- rider-editor/src/ui/filesystem/directory.rs | 501 ++++++++++---------- rider-editor/src/ui/filesystem/file.rs | 285 ++++++----- rider-editor/src/ui/icon.rs | 67 +++ rider-editor/src/ui/label.rs | 129 ++--- rider-editor/src/ui/menu_bar.rs | 2 +- rider-editor/src/ui/mod.rs | 59 +-- rider-editor/src/ui/project_tree/mod.rs | 2 +- 7 files changed, 564 insertions(+), 481 deletions(-) create mode 100644 rider-editor/src/ui/icon.rs diff --git a/rider-editor/src/ui/filesystem/directory.rs b/rider-editor/src/ui/filesystem/directory.rs index cff6f88..7fae3ae 100644 --- a/rider-editor/src/ui/filesystem/directory.rs +++ b/rider-editor/src/ui/filesystem/directory.rs @@ -1,7 +1,7 @@ use crate::app::*; use crate::renderer::*; +use crate::ui::icon::Icon; use crate::ui::*; -use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; use std::fs; use std::path; @@ -11,35 +11,234 @@ const CHILD_MARGIN: i32 = 4; const DEFAULT_ICON_SIZE: u32 = 16; pub struct DirectoryView { + inner: WidgetInner, opened: bool, expanded: bool, - name_width: u32, - icon_width: u32, - icon_height: u32, height: u32, path: String, files: Vec, directories: Vec, - pos: Point, - source: Rect, - config: ConfigAccess, + name_label: Label, + icon: Icon, +} + +impl std::ops::Deref for DirectoryView { + type Target = WidgetInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for DirectoryView { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Widget for DirectoryView { + fn texture_path(&self) -> Option { + None + } + + fn dest(&self) -> &Rect { + &self.dest + } + + fn set_dest(&mut self, _rect: &Rect) {} + + fn source(&self) -> &Rect { + &self.inner.source + } + + fn set_source(&mut self, _rect: &Rect) {} + + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult { + self.icon.update(ticks, context); + self.name_label.update(ticks, context); + if !path::Path::new(&self.path).exists() { + return UpdateResult::RefreshFsTree; + } + if self.opened { + for dir in self.directories.iter_mut() { + dir.update(ticks, context); + } + for file in self.files.iter_mut() { + file.update(ticks, context); + } + } + UpdateResult::NoOp + } + + fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult { + let dest = self.dest(); + let move_point = match context { + &UpdateContext::ParentPosition(p) => p.clone(), + _ => Point::new(0, 0), + }; + let dest = move_render_point(move_point.clone(), &dest); + + // icon or name is target of click + let icon_or_name = self.name_and_icon_rect(); + if move_render_point(move_point, &icon_or_name).contains_point(point.clone()) { + return UpdateResult::OpenDirectory(self.path.clone()); + } + + if !self.expanded { + return UpdateResult::NoOp; + } + + let mut p = dest.top_left() + + Point::new( + self.icon_width() as i32 + CHILD_MARGIN, + self.icon_height() as i32 + CHILD_MARGIN, + ); + for dir in self.directories.iter_mut() { + let context = UpdateContext::ParentPosition(p.clone()); + if dir.is_left_click_target(&point, &context) { + return dir.on_left_click(&point, &context); + } + p = p + Point::new(0, dir.height() as i32 + CHILD_MARGIN); + } + for file in self.files.iter_mut() { + let context = UpdateContext::ParentPosition(p.clone()); + if file.is_left_click_target(&point, &context) { + return file.on_left_click(); + } + p = p + Point::new(0, file.height() as i32 + CHILD_MARGIN); + } + + UpdateResult::NoOp + } + + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + let dest = self.dest(); + let move_point = match context { + UpdateContext::ParentPosition(p) => p.clone(), + _ => Point::new(0, 0), + }; + let dest = move_render_point(move_point.clone(), &dest); + + // icon or name is target of click + let name_and_icon_rect = self.name_and_icon_rect(); + if move_render_point(move_point.clone(), &name_and_icon_rect).contains_point(point.clone()) + { + return true; + } + if !self.expanded { + return false; + } + let mut p = dest.top_left() + + Point::new( + self.icon_width() as i32 + CHILD_MARGIN, + self.icon_height() as i32 + CHILD_MARGIN, + ); + // subdirectory is target of click + for dir in self.directories.iter() { + let context = UpdateContext::ParentPosition(p.clone()); + if dir.is_left_click_target(&point, &context) { + return true; + } + p = p + Point::new(0, dir.height() as i32 + CHILD_MARGIN); + } + // file inside directory is target of click + for file in self.files.iter() { + let context = UpdateContext::ParentPosition(p.clone()); + if file.is_left_click_target(&point, &context) { + return true; + } + p = p + Point::new(0, file.height() as i32 + CHILD_MARGIN); + } + false + } + + fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + where + R: Renderer + CharacterSizeManager, + C: CanvasAccess, + { + let mut dest = move_render_point( + match context { + &RenderContext::ParentPosition(p) => p.clone(), + _ => Point::new(0, 0), + }, + self.dest(), + ); + + self.icon.render( + canvas, + renderer, + &RenderContext::ParentPosition(Point::new(dest.x(), dest.y())), + ); + self.name_label.render( + canvas, + renderer, + &RenderContext::ParentPosition(Point::new( + dest.x() + self.icon_width() as i32, + dest.y(), + )), + ); + + self.render_children::(canvas, renderer, &mut dest); + } + + fn prepare_ui(&mut self, renderer: &mut R) + where + R: Renderer + CharacterSizeManager, + { + let size = renderer.load_character_size('W'); + self.icon.prepare_ui(renderer); + self.icon.dest.set_height(size.height()); + self.icon.dest.set_width(size.height()); + + self.name_label.prepare_ui(renderer); + if self.opened { + for dir in self.directories.iter_mut() { + dir.prepare_ui(renderer); + } + for file in self.files.iter_mut() { + file.prepare_ui(renderer); + } + } + self.calculate_size(renderer); + } } impl DirectoryView { pub fn new(path: String, config: ConfigAccess) -> Self { + let dir_texture_path = { + let c = config.read().unwrap(); + let mut themes_dir = c.directories().themes_dir.clone(); + let path = c.theme().images().directory_icon(); + themes_dir.push(path); + themes_dir.to_str().unwrap().to_owned() + }; + + let name = std::path::Path::new(&path) + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_owned(); Self { opened: false, expanded: false, - name_width: 0, - icon_width: DEFAULT_ICON_SIZE, - icon_height: DEFAULT_ICON_SIZE, height: 0, path, files: vec![], directories: vec![], - pos: Point::new(0, 0), - source: Rect::new(0, 0, 64, 64), - config, + inner: WidgetInner::new( + config.clone(), + Rect::new(0, 0, 64, 64), + Rect::new(0, 0, 0, 0), + ), + name_label: Label::new(name, config.clone()), + icon: Icon::new( + config, + dir_texture_path, + Rect::new(0, 0, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE), + Rect::new(0, 0, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE), + ), } } @@ -47,34 +246,24 @@ impl DirectoryView { self.path.clone() } - pub fn dest(&self) -> Rect { - match self.expanded { - true => Rect::new( - self.pos.x(), - self.pos.y(), - self.icon_width + self.name_width + NAME_MARGIN as u32, - self.height, - ), - false => Rect::new( - self.pos.x(), - self.pos.y(), - self.icon_width + self.name_width + NAME_MARGIN as u32, - self.icon_height, - ), - } + fn expand_view(&mut self) { + self.expanded = true; + self.dest = Rect::new( + self.dest.x(), + self.dest.y(), + self.icon_width() + self.name_width() + NAME_MARGIN as u32, + self.height, + ); } - // pub fn dest(&self) -> Rect { - // Rect::new( - // self.pos.x(), - // self.pos.y(), - // self.icon_width, - // self.icon_height, - // ) - // } - - pub fn source(&self) -> &Rect { - &self.source + fn collapse_view(&mut self) { + self.expanded = false; + self.dest = Rect::new( + self.dest.x(), + self.dest.y(), + self.icon_width() + self.name_width() + NAME_MARGIN as u32, + self.icon_height(), + ); } pub fn open_directory(&mut self, dir_path: String, renderer: &mut R) -> bool @@ -85,10 +274,12 @@ impl DirectoryView { _ if dir_path == self.path => { if !self.opened { self.opened = true; - self.expanded = true; + self.expand_view(); self.read_directory(renderer); + } else if self.expanded { + self.collapse_view(); } else { - self.expanded = !self.expanded; + self.expand_view(); } self.calculate_size(renderer); true @@ -96,7 +287,7 @@ impl DirectoryView { _ if dir_path.contains((self.path.clone() + "/").as_str()) => { if !self.opened { self.opened = true; - self.expanded = true; + self.expand_view(); self.read_directory(renderer); } for dir in self.directories.iter_mut() { @@ -124,18 +315,26 @@ impl DirectoryView { .to_owned() } + #[inline] pub fn name_width(&self) -> u32 { - self.name_width + self.name_label.name_width() } + #[inline] pub fn icon_width(&self) -> u32 { - self.icon_width + self.icon.width() } + #[inline] + pub fn icon_height(&self) -> u32 { + self.icon.height() + } + + #[inline] pub fn height(&self) -> u32 { match self.expanded { true => self.height, - false => self.icon_height, + false => self.icon.height(), } } @@ -182,62 +381,6 @@ impl DirectoryView { self.directories.sort_by(|a, b| a.name().cmp(&b.name())); } - fn render_icon(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect) - where - C: CanvasAccess, - R: Renderer, - { - let dir_texture_path = { - let c = self.config.read().unwrap(); - let mut themes_dir = c.directories().themes_dir.clone(); - let path = c.theme().images().directory_icon(); - themes_dir.push(path); - themes_dir.to_str().unwrap().to_owned() - }; - if let Ok(texture) = renderer.load_image(dir_texture_path.clone()) { - canvas - .render_image( - texture, - self.source.clone(), - Rect::new(dest.x(), dest.y(), self.icon_width, self.icon_height), - ) - .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); - } - } - - fn render_name(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect) - where - C: CanvasAccess, - R: Renderer + CharacterSizeManager, - { - let mut d = dest.clone(); - d.set_x(dest.x() + NAME_MARGIN); - let font_details = build_font_details(self); - let name = self.name(); - let config = self.config.read().unwrap(); - let text_color = config.theme().code_highlighting().title.color(); - - for c in name.chars() { - let size = renderer.load_character_size(c.clone()); - let mut text_details = TextDetails { - color: Color::RGBA(text_color.r, text_color.g, text_color.b, text_color.a), - text: c.to_string(), - font: font_details.clone(), - }; - let maybe_texture = renderer.load_text_tex(&mut text_details, font_details.clone()); - - if let Ok(texture) = maybe_texture { - d.set_width(size.width()); - d.set_height(size.height()); - - canvas - .render_image(texture, self.source.clone(), d.clone()) - .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); - d.set_x(d.x() + size.width() as i32); - } - } - } - fn render_children(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect) where C: CanvasAccess, @@ -248,8 +391,8 @@ impl DirectoryView { } let mut point = dest.top_left() + Point::new( - self.icon_width as i32 + CHILD_MARGIN, - self.icon_height as i32 + CHILD_MARGIN, + self.icon_width() as i32 + CHILD_MARGIN, + self.icon_height() as i32 + CHILD_MARGIN, ); for dir in self.directories.iter() { let context = RenderContext::ParentPosition(point.clone()); @@ -269,14 +412,6 @@ impl DirectoryView { { let size = renderer.load_character_size('W'); self.height = size.height(); - self.icon_height = size.height(); - self.icon_width = size.height(); - self.name_width = 0; - - for c in self.name().chars() { - let size = renderer.load_character_size(c.clone()); - self.name_width += size.width(); - } for dir in self.directories.iter_mut() { self.height = self.height + dir.height() + CHILD_MARGIN as u32; @@ -288,144 +423,12 @@ impl DirectoryView { fn name_and_icon_rect(&self) -> Rect { Rect::new( - self.pos.x(), - self.pos.y(), - self.icon_width + self.name_width + NAME_MARGIN as u32, - self.icon_height, + self.dest.x(), + self.dest.y(), + self.icon.width() + self.name_width() + NAME_MARGIN as u32, + self.icon.height(), ) } - - pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) - where - R: Renderer + CharacterSizeManager, - C: CanvasAccess, - { - let dest = self.dest(); - let move_point = match context { - &RenderContext::ParentPosition(p) => p.clone(), - _ => Point::new(0, 0), - }; - let mut dest = move_render_point(move_point, &dest); - self.render_icon::(canvas, renderer, &mut dest); - self.render_name::(canvas, renderer, &mut dest.clone()); - self.render_children::(canvas, renderer, &mut dest); - } - - pub fn prepare_ui(&mut self, renderer: &mut R) - where - R: Renderer + CharacterSizeManager, - { - if self.opened { - for dir in self.directories.iter_mut() { - dir.prepare_ui(renderer); - } - for file in self.files.iter_mut() { - file.prepare_ui(renderer); - } - } - self.calculate_size(renderer); - } - - pub fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult { - if !path::Path::new(&self.path).exists() { - return UpdateResult::RefreshFsTree; - } - if self.opened { - for dir in self.directories.iter_mut() { - dir.update(ticks, context); - } - for file in self.files.iter_mut() { - file.update(); - } - } - UpdateResult::NoOp - } - - pub fn render_start_point(&self) -> Point { - self.pos.clone() - } - - pub fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult { - let dest = self.dest(); - let move_point = match context { - &UpdateContext::ParentPosition(p) => p.clone(), - _ => Point::new(0, 0), - }; - let dest = move_render_point(move_point.clone(), &dest); - - // icon or name is target of click - let icon_or_name = self.name_and_icon_rect(); - if move_render_point(move_point, &icon_or_name).contains_point(point.clone()) { - return UpdateResult::OpenDirectory(self.path.clone()); - } - - if !self.expanded { - return UpdateResult::NoOp; - } - - let mut p = dest.top_left() - + Point::new( - self.icon_width as i32 + CHILD_MARGIN, - self.icon_height as i32 + CHILD_MARGIN, - ); - for dir in self.directories.iter_mut() { - let context = UpdateContext::ParentPosition(p.clone()); - if dir.is_left_click_target(&point, &context) { - return dir.on_left_click(&point, &context); - } - p = p + Point::new(0, dir.height() as i32 + CHILD_MARGIN); - } - for file in self.files.iter_mut() { - let context = UpdateContext::ParentPosition(p.clone()); - if file.is_left_click_target(&point, &context) { - return file.on_left_click(); - } - p = p + Point::new(0, file.height() as i32 + CHILD_MARGIN); - } - - UpdateResult::NoOp - } - - pub fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { - let dest = self.dest(); - let move_point = match context { - UpdateContext::ParentPosition(p) => p.clone(), - _ => Point::new(0, 0), - }; - let dest = move_render_point(move_point.clone(), &dest); - - // icon or name is target of click - let name_and_icon_rect = self.name_and_icon_rect(); - if move_render_point(move_point.clone(), &name_and_icon_rect).contains_point(point.clone()) - { - return true; - } - if !self.expanded { - return false; - } - let mut p = dest.top_left() - + Point::new( - self.icon_width as i32 + CHILD_MARGIN, - self.icon_height as i32 + CHILD_MARGIN, - ); - // subdirectory is target of click - for dir in self.directories.iter() { - let context = UpdateContext::ParentPosition(p.clone()); - if dir.is_left_click_target(&point, &context) { - return true; - } - p = p + Point::new(0, dir.height() as i32 + CHILD_MARGIN); - } - // file inside directory is target of click - for file in self.files.iter() { - let context = UpdateContext::ParentPosition(p.clone()); - if file.is_left_click_target(&point, &context) { - return true; - } - p = p + Point::new(0, file.height() as i32 + CHILD_MARGIN); - } - false - } } impl ConfigHolder for DirectoryView { @@ -569,7 +572,7 @@ mod tests { fn assert_initial_dest() { let config = build_config(); let widget = DirectoryView::new("/foo".to_owned(), config); - assert_eq!(widget.dest(), Rect::new(0, 0, 36, 16)); + assert_eq!(widget.dest(), &Rect::new(0, 0, 36, 16)); } #[test] @@ -578,7 +581,7 @@ mod tests { let mut renderer = SimpleRendererMock::new(config.clone()); let mut widget = DirectoryView::new("/foo".to_owned(), config); widget.prepare_ui(&mut renderer); - assert_eq!(widget.dest(), Rect::new(0, 0, 73, 14)); + assert_eq!(widget.dest(), &Rect::new(0, 0, 73, 14)); } //########################################################## diff --git a/rider-editor/src/ui/filesystem/file.rs b/rider-editor/src/ui/filesystem/file.rs index f78e2ac..539d878 100644 --- a/rider-editor/src/ui/filesystem/file.rs +++ b/rider-editor/src/ui/filesystem/file.rs @@ -1,9 +1,8 @@ use crate::app::*; use crate::renderer::*; +use crate::ui::icon::Icon; use crate::ui::*; -use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; -use std::collections::HashMap; use std::path; const ICON_DEST_WIDTH: u32 = 16; @@ -12,168 +11,151 @@ const ICON_SRC_WIDTH: u32 = 64; const ICON_SRC_HEIGHT: u32 = 64; pub struct FileEntry { - name_width: u32, - icon_width: u32, - height: u32, - name: String, path: String, - dest: Rect, - source: Rect, - config: ConfigAccess, - char_sizes: HashMap, + inner: WidgetInner, + icon: Icon, + label: Label, } -impl FileEntry { - pub fn new(name: String, path: String, config: ConfigAccess) -> Self { - Self { - name, - path, - name_width: 0, - icon_width: 0, - height: 0, - dest: Rect::new(0, 0, ICON_DEST_WIDTH, ICON_DEST_HEIGHT), - source: Rect::new(0, 0, ICON_SRC_WIDTH, ICON_SRC_HEIGHT), - config, - char_sizes: HashMap::new(), - } +impl std::ops::Deref for FileEntry { + type Target = WidgetInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for FileEntry { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Widget for FileEntry { + fn texture_path(&self) -> Option { + None } - pub fn name_width(&self) -> u32 { - self.name_width + fn dest(&self) -> &Rect { + &self.inner.dest } - pub fn icon_width(&self) -> u32 { - self.icon_width + fn set_dest(&mut self, rect: &Rect) { + self.inner.dest = rect.clone(); } - pub fn height(&self) -> u32 { - self.height + fn source(&self) -> &Rect { + &self.inner.source } - pub fn name(&self) -> String { - self.name.clone() + fn set_source(&mut self, rect: &Rect) { + self.inner.source = rect.clone(); } - pub fn path(&self) -> String { - self.path.clone() - } - - pub fn dest(&self) -> &Rect { - &self.dest - } - - pub fn source(&self) -> &Rect { - &self.source - } - - pub fn full_dest(&self) -> Rect { - Rect::new( - self.dest.x(), - self.dest.y(), - self.icon_width + NAME_MARGIN as u32 + self.name_width, - self.height, - ) - } - - fn render_icon(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect) - where - C: CanvasAccess, - R: Renderer, - { - let dir_texture_path = { - let c = self.config.read().unwrap(); - let mut themes_dir = c.directories().themes_dir.clone(); - let path = c.theme().images().file_icon(); - themes_dir.push(path); - themes_dir.to_str().unwrap().to_owned() - }; - let maybe_tex = renderer.load_image(dir_texture_path.clone()); - if let Ok(texture) = maybe_tex { - dest.set_width(ICON_DEST_WIDTH); - dest.set_height(ICON_DEST_HEIGHT); - canvas - .render_image(texture, self.source.clone(), dest.clone()) - .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); - } - } - - fn render_name(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect) - where - C: CanvasAccess, - R: Renderer, - { - let mut d = dest.clone(); - d.set_x(dest.x() + NAME_MARGIN); - - let font_details = build_font_details(self); - let name = self.name(); - - for c in name.chars() { - let size = self - .char_sizes - .get(&c) - .unwrap_or(&Rect::new(0, 0, 0, 0)) - .clone(); - let mut text_details = TextDetails { - color: Color::RGBA(255, 255, 255, 0), - text: c.to_string(), - font: font_details.clone(), - }; - let maybe_texture = renderer.load_text_tex(&mut text_details, font_details.clone()); - - if let Ok(texture) = maybe_texture { - d.set_width(size.width()); - d.set_height(size.height()); - - canvas - .render_image(texture, self.source.clone(), d.clone()) - .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); - d.set_x(d.x() + size.width() as i32) - } - } - } - - pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) - where - C: CanvasAccess, - R: Renderer, - { - let mut dest = match context { - &RenderContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest), - _ => self.dest.clone(), - }; - self.render_icon(canvas, renderer, &mut dest); - self.render_name(canvas, renderer, &mut dest.clone()); - } - - pub fn prepare_ui(&mut self, renderer: &mut R) - where - R: Renderer + CharacterSizeManager, - { - let w_rect = renderer.load_character_size('W'); - self.char_sizes.insert('W', w_rect.clone()); - self.height = w_rect.height(); - self.icon_width = w_rect.height(); - self.name_width = 0; - - for c in self.name().chars() { - let size = { renderer.load_character_size(c.clone()) }; - self.char_sizes.insert(c, size); - self.name_width += size.width(); - } - self.dest.set_width(w_rect.height()); - self.dest.set_height(w_rect.height()); - } - - pub fn update(&mut self) -> UpdateResult { + fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult { if !path::Path::new(&self.path).exists() { return UpdateResult::RefreshFsTree; } UpdateResult::NoOp } - pub fn render_start_point(&self) -> Point { - self.dest.top_left() + fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + where + C: CanvasAccess, + R: Renderer + CharacterSizeManager, + { + let dest = match context { + &RenderContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()), + _ => self.dest.clone(), + }; + self.icon.render( + canvas, + renderer, + &RenderContext::ParentPosition(Point::new(dest.x(), dest.y())), + ); + self.label.render( + canvas, + renderer, + &RenderContext::ParentPosition(Point::new(dest.x() + NAME_MARGIN, dest.y())), + ) + } + + fn prepare_ui(&mut self, renderer: &mut R) + where + R: Renderer + CharacterSizeManager, + { + let rect = renderer.load_character_size('W'); + self.icon.prepare_ui(renderer); + self.icon.dest.set_height(rect.height()); + self.icon.dest.set_width(rect.height()); + self.label.prepare_ui(renderer); + let old = self.inner.dest.clone(); + self.inner.dest = Rect::new( + old.x(), + old.y(), + self.name_width() + self.icon_width(), + self.height(), + ); + } +} + +impl FileEntry { + pub fn new(name: String, path: String, config: ConfigAccess) -> Self { + let texture_path = { + let c = config.read().unwrap(); + let mut themes_dir = c.directories().themes_dir.clone(); + let path = c.theme().images().file_icon(); + themes_dir.push(path); + themes_dir.to_str().unwrap().to_owned() + }; + Self { + path, + inner: WidgetInner::new( + config.clone(), + Rect::new(0, 0, ICON_SRC_WIDTH, ICON_SRC_HEIGHT), + Rect::new(0, 0, ICON_DEST_WIDTH, ICON_DEST_HEIGHT), + ), + icon: Icon::new( + config.clone(), + texture_path, + Rect::new(0, 0, ICON_SRC_WIDTH, ICON_SRC_HEIGHT), + Rect::new(0, 0, ICON_DEST_WIDTH, ICON_DEST_HEIGHT), + ), + label: Label::new(name.clone(), config), + } + } + + #[inline] + pub fn name_width(&self) -> u32 { + self.label.name_width() + } + + #[inline] + pub fn icon_width(&self) -> u32 { + self.icon.width() + } + + #[inline] + pub fn height(&self) -> u32 { + self.dest().height() + } + + #[inline] + pub fn name(&self) -> String { + self.label.name() + } + + pub fn path(&self) -> String { + self.path.clone() + } + + pub fn full_dest(&self) -> Rect { + Rect::new( + self.dest.x(), + self.dest.y(), + self.icon.width() + NAME_MARGIN as u32 + self.label.name_width(), + self.height(), + ) } pub fn on_left_click(&mut self) -> UpdateResult { @@ -184,7 +166,7 @@ impl FileEntry { let dest = Rect::new( self.dest.x(), self.dest.y(), - self.icon_width + self.name_width + NAME_MARGIN as u32, + self.icon_width() + self.name_width() + NAME_MARGIN as u32, self.dest.height(), ); let rect = match context { @@ -207,6 +189,7 @@ mod tests { use crate::tests::support::build_config; use crate::tests::support::CanvasMock; use crate::tests::support::SimpleRendererMock; + use crate::ui::{UpdateContext, Widget}; //########################################################## // name_width @@ -378,7 +361,10 @@ mod tests { let mut renderer = SimpleRendererMock::new(config.clone()); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); widget.prepare_ui(&mut renderer); - assert_eq!(widget.update(), UpdateResult::RefreshFsTree); + assert_eq!( + widget.update(0, &UpdateContext::Nothing), + UpdateResult::RefreshFsTree + ); } #[test] @@ -387,7 +373,10 @@ mod tests { let mut renderer = SimpleRendererMock::new(config.clone()); let mut widget = FileEntry::new("bar.txt".to_owned(), "/tmp".to_owned(), config); widget.prepare_ui(&mut renderer); - assert_eq!(widget.update(), UpdateResult::NoOp); + assert_eq!( + widget.update(0, &UpdateContext::Nothing), + UpdateResult::NoOp + ); } //########################################################## diff --git a/rider-editor/src/ui/icon.rs b/rider-editor/src/ui/icon.rs new file mode 100644 index 0000000..64ee836 --- /dev/null +++ b/rider-editor/src/ui/icon.rs @@ -0,0 +1,67 @@ +use crate::ui::{Widget, WidgetInner}; +use rider_config::ConfigAccess; +use sdl2::rect::Rect; + +pub struct Icon { + path: String, + inner: WidgetInner, +} + +impl std::ops::Deref for Icon { + type Target = WidgetInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for Icon { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Icon { + pub fn new(config: ConfigAccess, path: String, source: Rect, dest: Rect) -> Self { + Self { + path, + inner: WidgetInner::new(config, source, dest), + } + } + + #[inline] + pub fn height(&self) -> u32 { + self.inner.dest.height() + } + + #[inline] + pub fn width(&self) -> u32 { + self.inner.dest.width() + } + + pub fn set_texture_path(&mut self, path: String) { + self.path = path; + } +} + +impl Widget for Icon { + fn texture_path(&self) -> Option { + Some(self.path.clone()) + } + + fn dest(&self) -> &Rect { + &self.inner.dest + } + + fn set_dest(&mut self, rect: &Rect) { + self.inner.dest = rect.clone(); + } + + fn source(&self) -> &Rect { + &self.inner.source + } + + fn set_source(&mut self, rect: &Rect) { + self.inner.source = rect.clone(); + } +} diff --git a/rider-editor/src/ui/label.rs b/rider-editor/src/ui/label.rs index ff609a3..b30970c 100644 --- a/rider-editor/src/ui/label.rs +++ b/rider-editor/src/ui/label.rs @@ -2,7 +2,7 @@ use crate::app::*; use crate::renderer::*; use crate::ui::*; use sdl2::pixels::Color; -use sdl2::rect::{Point, Rect}; +use sdl2::rect::Rect; use std::collections::HashMap; const DEST_WIDTH: u32 = 16; @@ -11,37 +11,47 @@ const SRC_WIDTH: u32 = 64; const SRC_HEIGHT: u32 = 64; pub struct Label { - name_width: u32, - height: u32, name: String, - source: Rect, - dest: Rect, char_sizes: HashMap, - config: ConfigAccess, + inner: WidgetInner, } -impl Label { - pub fn new(name: String, config: ConfigAccess) -> Self { - Self { - name, - name_width: 0, - height: 0, - dest: Rect::new(0, 0, DEST_WIDTH, DEST_HEIGHT), - source: Rect::new(0, 0, SRC_WIDTH, SRC_HEIGHT), - config, - char_sizes: HashMap::new(), - } +impl std::ops::Deref for Label { + type Target = WidgetInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for Label { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Widget for Label { + fn texture_path(&self) -> Option { + None } - pub fn name_width(&self) -> u32 { - self.name_width + fn dest(&self) -> &Rect { + &self.inner.dest } - pub fn name(&self) -> String { - self.name.clone() + fn set_dest(&mut self, rect: &Rect) { + self.inner.dest = rect.clone(); } - pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + fn source(&self) -> &Rect { + &self.inner.source + } + + fn set_source(&mut self, rect: &Rect) { + self.inner.source = rect.clone(); + } + + fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) where C: CanvasAccess, R: Renderer, @@ -52,65 +62,78 @@ impl Label { }; let mut d = dest.clone(); d.set_x(dest.x() + NAME_MARGIN); + canvas.set_clipping(d.clone()); let font_details = build_font_details(self); for c in self.name.chars() { let size = self .char_sizes .get(&c) - .unwrap_or(&Rect::new(0, 0, 0, 0)) - .clone(); - let mut text_details = TextDetails { - color: Color::RGBA(255, 255, 255, 0), - text: c.to_string(), - font: font_details.clone(), - }; - let maybe_texture = renderer.load_text_tex(&mut text_details, font_details.clone()); + .cloned() + .unwrap_or_else(|| Rect::new(0, 0, 0, 0)); + renderer + .load_text_tex( + &mut TextDetails { + color: Color::RGBA(255, 255, 255, 0), + text: c.to_string(), + font: font_details.clone(), + }, + font_details.clone(), + ) + .and_then(|texture| { + d.set_width(size.width()); + d.set_height(size.height()); - if let Ok(texture) = maybe_texture { - d.set_width(size.width()); - d.set_height(size.height()); - - canvas - .render_image(texture, self.source.clone(), d.clone()) - .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); - d.set_x(d.x() + size.width() as i32); - } + canvas + .render_image(texture, self.source.clone(), d.clone()) + .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); + d.set_x(d.x() + size.width() as i32); + Ok(()) + }) + .unwrap_or_else(|e| { + eprintln!("Failed to render label \"{:?}\": {:?}", self.name(), e) + }) } } - pub fn prepare_ui(&mut self, renderer: &mut R) + fn prepare_ui(&mut self, renderer: &mut R) where R: Renderer + CharacterSizeManager, { let w_rect = renderer.load_character_size('W'); self.char_sizes.insert('W', w_rect.clone()); - self.height = w_rect.height(); - self.name_width = 0; + let mut name_width = 0; for c in self.name().chars() { let size = { renderer.load_character_size(c.clone()) }; self.char_sizes.insert(c, size); - self.name_width += size.width(); + name_width += size.width(); } - self.dest.set_width(w_rect.height()); + self.dest.set_width(name_width); self.dest.set_height(w_rect.height()); } } -impl RenderBox for Label { - fn render_start_point(&self) -> Point { - self.dest.top_left() +impl Label { + pub fn new(name: String, config: ConfigAccess) -> Self { + Self { + name, + char_sizes: HashMap::new(), + inner: WidgetInner::new( + config, + Rect::new(0, 0, SRC_WIDTH, SRC_HEIGHT), + Rect::new(0, 0, DEST_WIDTH, DEST_HEIGHT), + ), + } } - fn dest(&self) -> Rect { - self.dest.clone() + #[inline] + pub fn name_width(&self) -> u32 { + self.dest.width() } -} -impl Update for Label { - fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult { - UpdateResult::NoOp + pub fn name(&self) -> String { + self.name.clone() } } diff --git a/rider-editor/src/ui/menu_bar.rs b/rider-editor/src/ui/menu_bar.rs index 1184654..5824beb 100644 --- a/rider-editor/src/ui/menu_bar.rs +++ b/rider-editor/src/ui/menu_bar.rs @@ -45,7 +45,7 @@ impl MenuBar { pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) where C: CanvasAccess, - R: Renderer, + R: Renderer + CharacterSizeManager, { use std::borrow::*; diff --git a/rider-editor/src/ui/mod.rs b/rider-editor/src/ui/mod.rs index 9d536e5..488b1dd 100644 --- a/rider-editor/src/ui/mod.rs +++ b/rider-editor/src/ui/mod.rs @@ -13,6 +13,7 @@ pub mod caret; pub mod file; pub mod file_editor; pub mod filesystem; +pub mod icon; pub mod label; pub mod menu_bar; pub mod modal; @@ -160,35 +161,6 @@ impl WidgetInner { pub trait Widget { fn texture_path(&self) -> Option; - fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) - where - C: CanvasAccess, - R: Renderer, - { - let mut dest = match context { - &RenderContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest()), - _ => self.dest().clone(), - }; - - canvas.set_clipping(self.clipping(&dest)); - self.texture_path() - .and_then(|path| renderer.load_image(path).ok()) - .and_then(|texture| { - dest.set_width(self.dest().width()); - dest.set_height(self.dest().height()); - canvas - .render_image(texture.clone(), self.source().clone(), dest.clone()) - .unwrap_or_else(|_| panic!("Failed to draw widget texture")); - Some(()) - }); - } - - fn prepare_ui<'l, T>(&mut self, _renderer: &mut T) - where - T: ConfigHolder + Renderer, - { - } - fn dest(&self) -> &Rect; fn set_dest(&mut self, rect: &Rect); @@ -233,6 +205,35 @@ pub trait Widget { fn padding_height(&self) -> u32 { 0 } + + fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + where + C: CanvasAccess, + R: Renderer + CharacterSizeManager, + { + let mut dest = match context { + &RenderContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest()), + _ => self.dest().clone(), + }; + + canvas.set_clipping(self.clipping(&dest)); + self.texture_path() + .and_then(|path| renderer.load_image(path).ok()) + .and_then(|texture| { + dest.set_width(self.dest().width()); + dest.set_height(self.dest().height()); + canvas + .render_image(texture.clone(), self.source().clone(), dest.clone()) + .unwrap_or_else(|_| panic!("Failed to draw widget texture")); + Some(()) + }); + } + + fn prepare_ui<'l, T>(&mut self, _renderer: &mut T) + where + T: Renderer + CharacterSizeManager, + { + } } #[cfg(test)] diff --git a/rider-editor/src/ui/project_tree/mod.rs b/rider-editor/src/ui/project_tree/mod.rs index cde8b1e..1f1378e 100644 --- a/rider-editor/src/ui/project_tree/mod.rs +++ b/rider-editor/src/ui/project_tree/mod.rs @@ -4,11 +4,11 @@ use crate::ui::filesystem::directory::DirectoryView; use crate::ui::horizontal_scroll_bar::HorizontalScrollBar; use crate::ui::text_character::CharacterSizeManager; use crate::ui::vertical_scroll_bar::VerticalScrollBar; -use crate::ui::CanvasAccess; use crate::ui::ClickHandler; use crate::ui::RenderContext; use crate::ui::UpdateContext; use crate::ui::{move_render_point, ScrollView}; +use crate::ui::{CanvasAccess, Widget}; use rider_config::config::Config; use rider_config::ConfigHolder; use sdl2::pixels::Color;