diff --git a/.gitignore b/.gitignore index a9591cd..6892901 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,3 @@ log .codecov cobertura.xml cov - diff --git a/README.md b/README.md index b0ffaea..01dee9a 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ rustup run nightly cargo run -p rider-editor * [x] Handle caret movement with arrow keys * [x] Add text content * [x] Open file menu -* [ ] `Save file` with button -* [ ] `Save file` with shortcut +* [x] `Save file` with button +* [x] `Save file` with shortcut * [ ] `Save file as...` with shortcut * [x] Theme based menu UI * [x] Lock scroll when no available content diff --git a/rider-editor/src/app/app_state.rs b/rider-editor/src/app/app_state.rs index b7a63f7..fcedc8b 100644 --- a/rider-editor/src/app/app_state.rs +++ b/rider-editor/src/app/app_state.rs @@ -113,7 +113,8 @@ impl AppState { self.file_editor.render(canvas, renderer); // menu bar - self.menu_bar.render(canvas, &RenderContext::Nothing); + self.menu_bar + .render(canvas, renderer, &RenderContext::Nothing); // project tree self.project_tree.render(canvas, renderer); diff --git a/rider-editor/src/ui/menu_bar.rs b/rider-editor/src/ui/menu_bar.rs index ac8fd03..d1c9e80 100644 --- a/rider-editor/src/ui/menu_bar.rs +++ b/rider-editor/src/ui/menu_bar.rs @@ -1,14 +1,19 @@ use crate::app::UpdateResult as UR; +use crate::renderer::Renderer; use crate::ui::*; use rider_config::ConfigAccess; use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; +const SAVE_BUTTON_OFFSET_LEFT: i32 = 16; +const SAVE_BUTTON_OFFSET_TOP: i32 = 16; + pub struct MenuBar { border_color: Color, background_color: Color, dest: Rect, config: ConfigAccess, + save_button: SaveButton, } impl MenuBar { @@ -26,6 +31,7 @@ impl MenuBar { border_color, background_color, dest: Rect::new(0, 0, w as u32, h as u32), + save_button: SaveButton::new(config.clone()), config, } } @@ -34,19 +40,22 @@ impl MenuBar { &self.background_color } - pub fn render(&self, canvas: &mut C, context: &RenderContext) + pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) where C: CanvasAccess, + R: Renderer, { use std::borrow::*; + let relative_position = match context.borrow() { + RenderContext::RelativePosition(p) => p.clone(), + _ => Point::new(0, 0), + }; + canvas.set_clipping(self.dest.clone()); canvas .render_rect( - match context.borrow() { - RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest), - _ => self.dest(), - }, + move_render_point(relative_position.clone(), &self.dest), self.background_color.clone(), ) .unwrap_or_else(|_| panic!("Failed to draw main menu background")); @@ -61,6 +70,11 @@ impl MenuBar { self.border_color.clone(), ) .unwrap_or_else(|_| panic!("Failed to draw main menu background")); + + let context = RenderContext::RelativePosition( + relative_position.offset(SAVE_BUTTON_OFFSET_LEFT, SAVE_BUTTON_OFFSET_TOP), + ); + self.save_button.render(canvas, renderer, &context); } pub fn prepare_ui(&mut self) { @@ -79,7 +93,19 @@ impl Update for MenuBar { } impl ClickHandler for MenuBar { - fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR { + fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR { + use std::borrow::*; + + let relative_position = match context.borrow() { + UpdateContext::ParentPosition(p) => p.clone(), + _ => Point::new(0, 0), + }; + let context = UpdateContext::ParentPosition( + relative_position.offset(SAVE_BUTTON_OFFSET_LEFT, SAVE_BUTTON_OFFSET_TOP), + ); + if self.save_button.is_left_click_target(point, &context) { + return self.save_button.on_left_click(point, &context); + } UR::NoOp } @@ -218,6 +244,7 @@ mod test_click_handler { #[cfg(test)] mod test_render { + use crate::tests::support::SimpleRendererMock; use crate::tests::*; use crate::ui::*; use sdl2::pixels::Color; @@ -277,9 +304,10 @@ mod test_render { border_rect: Rect::new(0, 0, 0, 0), border_color: Color::RGB(0, 0, 0), }; + let mut renderer = SimpleRendererMock::new(config.clone()); let mut widget = MenuBar::new(Arc::clone(&config)); widget.prepare_ui(); - widget.render(&mut canvas, &context); + widget.render(&mut canvas, &mut renderer, &context); assert_eq!(widget.dest(), Rect::new(0, 0, 1024, 60)); let expected = CanvasMock { clipping: Rect::new(0, 0, 1024, 60), diff --git a/rider-editor/src/ui/mod.rs b/rider-editor/src/ui/mod.rs index 3d7d19e..1993988 100644 --- a/rider-editor/src/ui/mod.rs +++ b/rider-editor/src/ui/mod.rs @@ -15,6 +15,7 @@ pub mod filesystem; pub mod menu_bar; pub mod modal; pub mod project_tree; +pub mod save_button; pub mod scroll_bar; pub mod text_character; @@ -25,6 +26,7 @@ pub use crate::ui::filesystem::*; pub use crate::ui::menu_bar::*; pub use crate::ui::modal::*; pub use crate::ui::project_tree::*; +pub use crate::ui::save_button::*; pub use crate::ui::scroll_bar::*; pub use crate::ui::text_character::*; diff --git a/rider-editor/src/ui/save_button.rs b/rider-editor/src/ui/save_button.rs new file mode 100644 index 0000000..2834fd4 --- /dev/null +++ b/rider-editor/src/ui/save_button.rs @@ -0,0 +1,106 @@ +use crate::app::{ConfigAccess, ConfigHolder, UpdateResult as UR}; +use crate::renderer::Renderer; +use crate::ui::{ + move_render_point, CanvasAccess, ClickHandler, RenderBox, RenderContext, Update, UpdateContext, +}; +use sdl2::rect::{Point, Rect}; + +const ICON_DEST_WIDTH: u32 = 32; +const ICON_DEST_HEIGHT: u32 = 32; +const ICON_SRC_WIDTH: u32 = 64; +const ICON_SRC_HEIGHT: u32 = 64; + +pub struct SaveButton { + source: Rect, + dest: Rect, + config: ConfigAccess, +} + +impl SaveButton { + pub fn new(config: ConfigAccess) -> Self { + Self { + dest: Rect::new(0, 0, ICON_DEST_WIDTH, ICON_DEST_HEIGHT), + source: Rect::new(0, 0, ICON_SRC_WIDTH, ICON_SRC_HEIGHT), + config, + } + } + + pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + where + C: CanvasAccess, + R: Renderer, + { + use std::borrow::*; + let mut dest = match context { + &RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest), + _ => self.dest.clone(), + }; + + canvas.set_clipping(dest.clone()); + let save_texture_path = { + let c = self.config.read().unwrap(); + let mut themes_dir = c.directories().themes_dir.clone(); + let path = c.theme().images().save_icon(); + themes_dir.push(path); + themes_dir.to_str().unwrap().to_owned() + }; + let maybe_tex = renderer.load_image(save_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")); + } + } + + pub fn prepare_ui<'l, T>(&mut self, _renderer: &mut T) + where + T: ConfigHolder + Renderer, + { + } + + pub fn source(&self) -> &Rect { + &self.source + } + + pub fn set_dest(&mut self, rect: &Rect) { + self.dest = rect.clone(); + } + + pub fn set_source(&mut self, rect: &Rect) { + self.source = rect.clone(); + } +} + +impl Update for SaveButton { + fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR { + let config = self.config.read().unwrap(); + self.dest.set_width(config.width()); + UR::NoOp + } +} + +impl ClickHandler for SaveButton { + fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR { + UR::SaveCurrentFile + } + + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + match *context { + UpdateContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest), + _ => self.dest(), + } + .contains_point(point.clone()) + } +} + +impl RenderBox for SaveButton { + fn render_start_point(&self) -> Point { + self.dest.top_left() + } + + fn dest(&self) -> Rect { + self.dest + } +} diff --git a/rider-generator/assets/themes/default/images/save-48x84.png b/rider-generator/assets/themes/default/images/save-48x84.png new file mode 100644 index 0000000..ca55ea0 Binary files /dev/null and b/rider-generator/assets/themes/default/images/save-48x84.png differ diff --git a/rider-generator/assets/themes/default/images/save-512x512.png b/rider-generator/assets/themes/default/images/save-512x512.png new file mode 100644 index 0000000..7d62924 Binary files /dev/null and b/rider-generator/assets/themes/default/images/save-512x512.png differ diff --git a/rider-generator/assets/themes/default/images/save-64x64.png b/rider-generator/assets/themes/default/images/save-64x64.png new file mode 100644 index 0000000..55fa15e Binary files /dev/null and b/rider-generator/assets/themes/default/images/save-64x64.png differ diff --git a/rider-generator/assets/themes/railscasts/images/save-48x84.png b/rider-generator/assets/themes/railscasts/images/save-48x84.png new file mode 100644 index 0000000..ca55ea0 Binary files /dev/null and b/rider-generator/assets/themes/railscasts/images/save-48x84.png differ diff --git a/rider-generator/assets/themes/railscasts/images/save-512x512.png b/rider-generator/assets/themes/railscasts/images/save-512x512.png new file mode 100644 index 0000000..7d62924 Binary files /dev/null and b/rider-generator/assets/themes/railscasts/images/save-512x512.png differ diff --git a/rider-generator/assets/themes/railscasts/images/save-64x64.png b/rider-generator/assets/themes/railscasts/images/save-64x64.png new file mode 100644 index 0000000..55fa15e Binary files /dev/null and b/rider-generator/assets/themes/railscasts/images/save-64x64.png differ diff --git a/rider-generator/src/images.rs b/rider-generator/src/images.rs index 5726483..3a7e6da 100644 --- a/rider-generator/src/images.rs +++ b/rider-generator/src/images.rs @@ -47,6 +47,12 @@ fn create_railscasts_file_icon(dir: &PathBuf) -> std::io::Result<()> { Ok(()) } +fn create_railscasts_save_icon(dir: &PathBuf) -> std::io::Result<()> { + let blob = include_bytes!("../assets/themes/railscasts/images/save-64x64.png"); + write_bytes_to(dir, "save-64x64.png", blob)?; + Ok(()) +} + fn railscasts_theme(directories: &Directories) -> std::io::Result<()> { let mut dir = PathBuf::new(); dir.push(directories.themes_dir.clone()); @@ -55,6 +61,7 @@ fn railscasts_theme(directories: &Directories) -> std::io::Result<()> { create_dir_all(&dir)?; create_railscasts_directory_icon(&dir)?; create_railscasts_file_icon(&dir)?; + create_railscasts_save_icon(&dir)?; Ok(()) } diff --git a/rider-themes/src/images.rs b/rider-themes/src/images.rs index ad9e833..46148e7 100644 --- a/rider-themes/src/images.rs +++ b/rider-themes/src/images.rs @@ -2,13 +2,15 @@ pub struct ThemeImages { directory_icon: String, file_icon: String, + save_icon: String, } impl ThemeImages { - pub fn new(directory_icon: String, file_icon: String) -> Self { + pub fn new(directory_icon: String, file_icon: String, save_icon: String) -> Self { Self { file_icon, directory_icon, + save_icon, } } @@ -19,6 +21,10 @@ impl ThemeImages { pub fn file_icon(&self) -> String { self.file_icon.clone() } + + pub fn save_icon(&self) -> String { + self.save_icon.clone() + } } impl Default for ThemeImages { @@ -26,6 +32,7 @@ impl Default for ThemeImages { Self { directory_icon: "default/images/directory-64x64.png".to_string(), file_icon: "default/images/file-64x64.png".to_string(), + save_icon: "default/images/save-64x64.png".to_string(), } } } @@ -36,16 +43,25 @@ mod test { #[test] fn assert_directory_icon() { - let config = ThemeImages::new("foo".to_owned(), "bar".to_owned()); + let config = ThemeImages::new("foo".to_owned(), "bar".to_owned(), "baz".to_owned()); let result = config.directory_icon(); let expected = "foo".to_owned(); assert_eq!(result, expected); } + #[test] fn assert_file_icon() { - let config = ThemeImages::new("foo".to_owned(), "bar".to_owned()); + let config = ThemeImages::new("foo".to_owned(), "bar".to_owned(), "baz".to_owned()); let result = config.file_icon(); let expected = "bar".to_owned(); assert_eq!(result, expected); } + + #[test] + fn assert_save_icon() { + let config = ThemeImages::new("foo".to_owned(), "bar".to_owned(), "baz".to_owned()); + let result = config.save_icon(); + let expected = "baz".to_owned(); + assert_eq!(result, expected); + } } diff --git a/rider-themes/src/predef/railscasts.rs b/rider-themes/src/predef/railscasts.rs index 29b6c07..9959bb3 100644 --- a/rider-themes/src/predef/railscasts.rs +++ b/rider-themes/src/predef/railscasts.rs @@ -44,6 +44,7 @@ pub fn build_theme() -> Theme { ThemeImages::new( "railscasts/images/directory-64x64.png".to_owned(), "railscasts/images/file-64x64.png".to_owned(), + "railscasts/images/save-64x64.png".to_owned(), ), ) }