Simplify components

This commit is contained in:
Adrian Wozniak 2019-12-22 14:15:47 +01:00
parent a0d7f4acec
commit a80b3d0107
7 changed files with 564 additions and 481 deletions

View File

@ -1,7 +1,7 @@
use crate::app::*; use crate::app::*;
use crate::renderer::*; use crate::renderer::*;
use crate::ui::icon::Icon;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use std::fs; use std::fs;
use std::path; use std::path;
@ -11,35 +11,234 @@ const CHILD_MARGIN: i32 = 4;
const DEFAULT_ICON_SIZE: u32 = 16; const DEFAULT_ICON_SIZE: u32 = 16;
pub struct DirectoryView { pub struct DirectoryView {
inner: WidgetInner,
opened: bool, opened: bool,
expanded: bool, expanded: bool,
name_width: u32,
icon_width: u32,
icon_height: u32,
height: u32, height: u32,
path: String, path: String,
files: Vec<FileEntry>, files: Vec<FileEntry>,
directories: Vec<DirectoryView>, directories: Vec<DirectoryView>,
pos: Point, name_label: Label,
source: Rect, icon: Icon,
config: ConfigAccess, }
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<String> {
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<C, R>(&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::<C, R>(canvas, renderer, &mut dest);
}
fn prepare_ui<R>(&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 { impl DirectoryView {
pub fn new(path: String, config: ConfigAccess) -> Self { 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 { Self {
opened: false, opened: false,
expanded: false, expanded: false,
name_width: 0,
icon_width: DEFAULT_ICON_SIZE,
icon_height: DEFAULT_ICON_SIZE,
height: 0, height: 0,
path, path,
files: vec![], files: vec![],
directories: vec![], directories: vec![],
pos: Point::new(0, 0), inner: WidgetInner::new(
source: Rect::new(0, 0, 64, 64), 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, 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() self.path.clone()
} }
pub fn dest(&self) -> Rect { fn expand_view(&mut self) {
match self.expanded { self.expanded = true;
true => Rect::new( self.dest = Rect::new(
self.pos.x(), self.dest.x(),
self.pos.y(), self.dest.y(),
self.icon_width + self.name_width + NAME_MARGIN as u32, self.icon_width() + self.name_width() + NAME_MARGIN as u32,
self.height, self.height,
), );
false => Rect::new(
self.pos.x(),
self.pos.y(),
self.icon_width + self.name_width + NAME_MARGIN as u32,
self.icon_height,
),
}
} }
// pub fn dest(&self) -> Rect { fn collapse_view(&mut self) {
// Rect::new( self.expanded = false;
// self.pos.x(), self.dest = Rect::new(
// self.pos.y(), self.dest.x(),
// self.icon_width, self.dest.y(),
// self.icon_height, self.icon_width() + self.name_width() + NAME_MARGIN as u32,
// ) self.icon_height(),
// } );
pub fn source(&self) -> &Rect {
&self.source
} }
pub fn open_directory<R>(&mut self, dir_path: String, renderer: &mut R) -> bool pub fn open_directory<R>(&mut self, dir_path: String, renderer: &mut R) -> bool
@ -85,10 +274,12 @@ impl DirectoryView {
_ if dir_path == self.path => { _ if dir_path == self.path => {
if !self.opened { if !self.opened {
self.opened = true; self.opened = true;
self.expanded = true; self.expand_view();
self.read_directory(renderer); self.read_directory(renderer);
} else if self.expanded {
self.collapse_view();
} else { } else {
self.expanded = !self.expanded; self.expand_view();
} }
self.calculate_size(renderer); self.calculate_size(renderer);
true true
@ -96,7 +287,7 @@ impl DirectoryView {
_ if dir_path.contains((self.path.clone() + "/").as_str()) => { _ if dir_path.contains((self.path.clone() + "/").as_str()) => {
if !self.opened { if !self.opened {
self.opened = true; self.opened = true;
self.expanded = true; self.expand_view();
self.read_directory(renderer); self.read_directory(renderer);
} }
for dir in self.directories.iter_mut() { for dir in self.directories.iter_mut() {
@ -124,18 +315,26 @@ impl DirectoryView {
.to_owned() .to_owned()
} }
#[inline]
pub fn name_width(&self) -> u32 { pub fn name_width(&self) -> u32 {
self.name_width self.name_label.name_width()
} }
#[inline]
pub fn icon_width(&self) -> u32 { 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 { pub fn height(&self) -> u32 {
match self.expanded { match self.expanded {
true => self.height, 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())); self.directories.sort_by(|a, b| a.name().cmp(&b.name()));
} }
fn render_icon<C, R>(&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<C, R>(&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<C, R>(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect) fn render_children<C, R>(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect)
where where
C: CanvasAccess, C: CanvasAccess,
@ -248,8 +391,8 @@ impl DirectoryView {
} }
let mut point = dest.top_left() let mut point = dest.top_left()
+ Point::new( + Point::new(
self.icon_width as i32 + CHILD_MARGIN, self.icon_width() as i32 + CHILD_MARGIN,
self.icon_height as i32 + CHILD_MARGIN, self.icon_height() as i32 + CHILD_MARGIN,
); );
for dir in self.directories.iter() { for dir in self.directories.iter() {
let context = RenderContext::ParentPosition(point.clone()); let context = RenderContext::ParentPosition(point.clone());
@ -269,14 +412,6 @@ impl DirectoryView {
{ {
let size = renderer.load_character_size('W'); let size = renderer.load_character_size('W');
self.height = size.height(); 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() { for dir in self.directories.iter_mut() {
self.height = self.height + dir.height() + CHILD_MARGIN as u32; self.height = self.height + dir.height() + CHILD_MARGIN as u32;
@ -288,144 +423,12 @@ impl DirectoryView {
fn name_and_icon_rect(&self) -> Rect { fn name_and_icon_rect(&self) -> Rect {
Rect::new( Rect::new(
self.pos.x(), self.dest.x(),
self.pos.y(), self.dest.y(),
self.icon_width + self.name_width + NAME_MARGIN as u32, self.icon.width() + self.name_width() + NAME_MARGIN as u32,
self.icon_height, self.icon.height(),
) )
} }
pub fn render<R, C>(&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::<C, R>(canvas, renderer, &mut dest);
self.render_name::<C, R>(canvas, renderer, &mut dest.clone());
self.render_children::<C, R>(canvas, renderer, &mut dest);
}
pub fn prepare_ui<R>(&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 { impl ConfigHolder for DirectoryView {
@ -569,7 +572,7 @@ mod tests {
fn assert_initial_dest() { fn assert_initial_dest() {
let config = build_config(); let config = build_config();
let widget = DirectoryView::new("/foo".to_owned(), 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] #[test]
@ -578,7 +581,7 @@ mod tests {
let mut renderer = SimpleRendererMock::new(config.clone()); let mut renderer = SimpleRendererMock::new(config.clone());
let mut widget = DirectoryView::new("/foo".to_owned(), config); let mut widget = DirectoryView::new("/foo".to_owned(), config);
widget.prepare_ui(&mut renderer); 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));
} }
//########################################################## //##########################################################

View File

@ -1,9 +1,8 @@
use crate::app::*; use crate::app::*;
use crate::renderer::*; use crate::renderer::*;
use crate::ui::icon::Icon;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use std::collections::HashMap;
use std::path; use std::path;
const ICON_DEST_WIDTH: u32 = 16; const ICON_DEST_WIDTH: u32 = 16;
@ -12,168 +11,151 @@ const ICON_SRC_WIDTH: u32 = 64;
const ICON_SRC_HEIGHT: u32 = 64; const ICON_SRC_HEIGHT: u32 = 64;
pub struct FileEntry { pub struct FileEntry {
name_width: u32,
icon_width: u32,
height: u32,
name: String,
path: String, path: String,
dest: Rect, inner: WidgetInner,
source: Rect, icon: Icon,
config: ConfigAccess, label: Label,
char_sizes: HashMap<char, Rect>,
} }
impl FileEntry { impl std::ops::Deref for FileEntry {
pub fn new(name: String, path: String, config: ConfigAccess) -> Self { type Target = WidgetInner;
Self {
name, fn deref(&self) -> &Self::Target {
path, &self.inner
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(),
} }
} }
pub fn name_width(&self) -> u32 { impl std::ops::DerefMut for FileEntry {
self.name_width fn deref_mut(&mut self) -> &mut Self::Target {
} &mut self.inner
pub fn icon_width(&self) -> u32 {
self.icon_width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn name(&self) -> String {
self.name.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<C, R>(&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<C, R>(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect) impl Widget for FileEntry {
where fn texture_path(&self) -> Option<String> {
C: CanvasAccess, None
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<C, R>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) fn dest(&self) -> &Rect {
where &self.inner.dest
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<R>(&mut self, renderer: &mut R) fn set_dest(&mut self, rect: &Rect) {
where self.inner.dest = rect.clone();
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 source(&self) -> &Rect {
&self.inner.source
}
fn set_source(&mut self, rect: &Rect) {
self.inner.source = rect.clone();
}
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult {
if !path::Path::new(&self.path).exists() { if !path::Path::new(&self.path).exists() {
return UpdateResult::RefreshFsTree; return UpdateResult::RefreshFsTree;
} }
UpdateResult::NoOp UpdateResult::NoOp
} }
pub fn render_start_point(&self) -> Point { fn render<C, R>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext)
self.dest.top_left() 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<R>(&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 { pub fn on_left_click(&mut self) -> UpdateResult {
@ -184,7 +166,7 @@ impl FileEntry {
let dest = Rect::new( let dest = Rect::new(
self.dest.x(), self.dest.x(),
self.dest.y(), 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(), self.dest.height(),
); );
let rect = match context { let rect = match context {
@ -207,6 +189,7 @@ mod tests {
use crate::tests::support::build_config; use crate::tests::support::build_config;
use crate::tests::support::CanvasMock; use crate::tests::support::CanvasMock;
use crate::tests::support::SimpleRendererMock; use crate::tests::support::SimpleRendererMock;
use crate::ui::{UpdateContext, Widget};
//########################################################## //##########################################################
// name_width // name_width
@ -378,7 +361,10 @@ mod tests {
let mut renderer = SimpleRendererMock::new(config.clone()); let mut renderer = SimpleRendererMock::new(config.clone());
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config); let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
widget.prepare_ui(&mut renderer); widget.prepare_ui(&mut renderer);
assert_eq!(widget.update(), UpdateResult::RefreshFsTree); assert_eq!(
widget.update(0, &UpdateContext::Nothing),
UpdateResult::RefreshFsTree
);
} }
#[test] #[test]
@ -387,7 +373,10 @@ mod tests {
let mut renderer = SimpleRendererMock::new(config.clone()); let mut renderer = SimpleRendererMock::new(config.clone());
let mut widget = FileEntry::new("bar.txt".to_owned(), "/tmp".to_owned(), config); let mut widget = FileEntry::new("bar.txt".to_owned(), "/tmp".to_owned(), config);
widget.prepare_ui(&mut renderer); widget.prepare_ui(&mut renderer);
assert_eq!(widget.update(), UpdateResult::NoOp); assert_eq!(
widget.update(0, &UpdateContext::Nothing),
UpdateResult::NoOp
);
} }
//########################################################## //##########################################################

View File

@ -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<String> {
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();
}
}

View File

@ -2,7 +2,7 @@ use crate::app::*;
use crate::renderer::*; use crate::renderer::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::Color; use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect}; use sdl2::rect::Rect;
use std::collections::HashMap; use std::collections::HashMap;
const DEST_WIDTH: u32 = 16; const DEST_WIDTH: u32 = 16;
@ -11,37 +11,47 @@ const SRC_WIDTH: u32 = 64;
const SRC_HEIGHT: u32 = 64; const SRC_HEIGHT: u32 = 64;
pub struct Label { pub struct Label {
name_width: u32,
height: u32,
name: String, name: String,
source: Rect,
dest: Rect,
char_sizes: HashMap<char, Rect>, char_sizes: HashMap<char, Rect>,
config: ConfigAccess, inner: WidgetInner,
} }
impl Label { impl std::ops::Deref for Label {
pub fn new(name: String, config: ConfigAccess) -> Self { type Target = WidgetInner;
Self {
name, fn deref(&self) -> &Self::Target {
name_width: 0, &self.inner
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(),
} }
} }
pub fn name_width(&self) -> u32 { impl std::ops::DerefMut for Label {
self.name_width fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
} }
pub fn name(&self) -> String { impl Widget for Label {
self.name.clone() fn texture_path(&self) -> Option<String> {
None
} }
pub fn render<C, R>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) 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();
}
fn render<C, R>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext)
where where
C: CanvasAccess, C: CanvasAccess,
R: Renderer, R: Renderer,
@ -52,22 +62,25 @@ impl Label {
}; };
let mut d = dest.clone(); let mut d = dest.clone();
d.set_x(dest.x() + NAME_MARGIN); d.set_x(dest.x() + NAME_MARGIN);
canvas.set_clipping(d.clone());
let font_details = build_font_details(self); let font_details = build_font_details(self);
for c in self.name.chars() { for c in self.name.chars() {
let size = self let size = self
.char_sizes .char_sizes
.get(&c) .get(&c)
.unwrap_or(&Rect::new(0, 0, 0, 0)) .cloned()
.clone(); .unwrap_or_else(|| Rect::new(0, 0, 0, 0));
let mut text_details = TextDetails { renderer
.load_text_tex(
&mut TextDetails {
color: Color::RGBA(255, 255, 255, 0), color: Color::RGBA(255, 255, 255, 0),
text: c.to_string(), text: c.to_string(),
font: font_details.clone(), font: font_details.clone(),
}; },
let maybe_texture = renderer.load_text_tex(&mut text_details, font_details.clone()); font_details.clone(),
)
if let Ok(texture) = maybe_texture { .and_then(|texture| {
d.set_width(size.width()); d.set_width(size.width());
d.set_height(size.height()); d.set_height(size.height());
@ -75,42 +88,52 @@ impl Label {
.render_image(texture, self.source.clone(), d.clone()) .render_image(texture, self.source.clone(), d.clone())
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); .unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
d.set_x(d.x() + size.width() as i32); 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<R>(&mut self, renderer: &mut R) fn prepare_ui<R>(&mut self, renderer: &mut R)
where where
R: Renderer + CharacterSizeManager, R: Renderer + CharacterSizeManager,
{ {
let w_rect = renderer.load_character_size('W'); let w_rect = renderer.load_character_size('W');
self.char_sizes.insert('W', w_rect.clone()); self.char_sizes.insert('W', w_rect.clone());
self.height = w_rect.height(); let mut name_width = 0;
self.name_width = 0;
for c in self.name().chars() { for c in self.name().chars() {
let size = { renderer.load_character_size(c.clone()) }; let size = { renderer.load_character_size(c.clone()) };
self.char_sizes.insert(c, size); 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()); self.dest.set_height(w_rect.height());
} }
} }
impl RenderBox for Label { impl Label {
fn render_start_point(&self) -> Point { pub fn new(name: String, config: ConfigAccess) -> Self {
self.dest.top_left() Self {
} name,
char_sizes: HashMap::new(),
fn dest(&self) -> Rect { inner: WidgetInner::new(
self.dest.clone() config,
Rect::new(0, 0, SRC_WIDTH, SRC_HEIGHT),
Rect::new(0, 0, DEST_WIDTH, DEST_HEIGHT),
),
} }
} }
impl Update for Label { #[inline]
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult { pub fn name_width(&self) -> u32 {
UpdateResult::NoOp self.dest.width()
}
pub fn name(&self) -> String {
self.name.clone()
} }
} }

View File

@ -45,7 +45,7 @@ impl MenuBar {
pub fn render<C, R>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) pub fn render<C, R>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext)
where where
C: CanvasAccess, C: CanvasAccess,
R: Renderer, R: Renderer + CharacterSizeManager,
{ {
use std::borrow::*; use std::borrow::*;

View File

@ -13,6 +13,7 @@ pub mod caret;
pub mod file; pub mod file;
pub mod file_editor; pub mod file_editor;
pub mod filesystem; pub mod filesystem;
pub mod icon;
pub mod label; pub mod label;
pub mod menu_bar; pub mod menu_bar;
pub mod modal; pub mod modal;
@ -160,35 +161,6 @@ impl WidgetInner {
pub trait Widget { pub trait Widget {
fn texture_path(&self) -> Option<String>; fn texture_path(&self) -> Option<String>;
fn render<C, R>(&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 dest(&self) -> &Rect;
fn set_dest(&mut self, rect: &Rect); fn set_dest(&mut self, rect: &Rect);
@ -233,6 +205,35 @@ pub trait Widget {
fn padding_height(&self) -> u32 { fn padding_height(&self) -> u32 {
0 0
} }
fn render<C, R>(&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)] #[cfg(test)]

View File

@ -4,11 +4,11 @@ use crate::ui::filesystem::directory::DirectoryView;
use crate::ui::horizontal_scroll_bar::HorizontalScrollBar; use crate::ui::horizontal_scroll_bar::HorizontalScrollBar;
use crate::ui::text_character::CharacterSizeManager; use crate::ui::text_character::CharacterSizeManager;
use crate::ui::vertical_scroll_bar::VerticalScrollBar; use crate::ui::vertical_scroll_bar::VerticalScrollBar;
use crate::ui::CanvasAccess;
use crate::ui::ClickHandler; use crate::ui::ClickHandler;
use crate::ui::RenderContext; use crate::ui::RenderContext;
use crate::ui::UpdateContext; use crate::ui::UpdateContext;
use crate::ui::{move_render_point, ScrollView}; use crate::ui::{move_render_point, ScrollView};
use crate::ui::{CanvasAccess, Widget};
use rider_config::config::Config; use rider_config::config::Config;
use rider_config::ConfigHolder; use rider_config::ConfigHolder;
use sdl2::pixels::Color; use sdl2::pixels::Color;