pub mod truncated_text;
use crate::controls::play_list::truncated_text::TruncatedText;
use crate::drawing::{Drawable, Plan, RoundedRectangle};
use crate::layouts::ListLayout;
use crate::layouts::ListLayoutElement;
use crate::layouts::{EdgeLayout, EdgeLayoutElement, Edges, HasPlanner, Layout};
use crate::story_board::{StoryBoard, easing};
#[allow(deprecated)]
use skia_safe::{Canvas, Color, Color4f, Font, Paint, Point, Rect, TileMode, gradient_shader};
use std::cell::RefCell;
use std::rc::Rc;
pub struct PlaylistItem {
planner: RefCell<Option<Rc<dyn Layout>>>,
internal_layout: Rc<EdgeLayout>,
text: Rc<TruncatedText>,
sel_bg: Rc<RoundedRectangle>,
indicator: Rc<RoundedRectangle>,
is_selected: RefCell<bool>,
sel_text_paint: Paint,
unsel_text_paint: Paint,
}
impl HasPlanner for PlaylistItem {
fn planner(&self) -> &RefCell<Option<Rc<dyn Layout>>> {
&self.planner
}
fn set_planner(&self, p: Option<Rc<dyn Layout>>) {
*self.planner.borrow_mut() = p;
}
}
impl Layout for PlaylistItem {
fn measure_child(&self, _c: &dyn Drawable, canvas: &Canvas) -> Rect {
self.get_planned_drawing_area(canvas)
}
}
impl Drawable for PlaylistItem {
fn draw(&self, canvas: &Canvas) -> anyhow::Result<()> {
self.internal_layout.draw(canvas)
}
fn get_horizontal_plan(&self) -> Plan {
Plan::Fill
}
fn get_vertical_plan(&self) -> Plan {
Plan::Fill
}
}
impl PlaylistItem {
#[allow(clippy::too_many_arguments)]
pub fn new(
text: &str,
font: Font,
dot_w: f32,
is_selected: bool,
sel_bg_paint: Paint,
indicator_paint: Paint,
sel_text_paint: Paint,
unsel_text_paint: Paint,
) -> Rc<Self> {
let internal_layout = EdgeLayout::new();
let initial_text_paint = if is_selected {
sel_text_paint.clone()
} else {
unsel_text_paint.clone()
};
let text_comp = TruncatedText::new(text, font, initial_text_paint, dot_w);
let sel_bg = RoundedRectangle::new(None, None, 6.0, 6.0, sel_bg_paint);
let indicator = RoundedRectangle::new(None, None, 2.0, 2.0, indicator_paint);
let text_l_margin = if is_selected { 14.0 } else { 10.0 };
internal_layout.add_child(
"text",
EdgeLayoutElement {
drawable: text_comp.clone(),
l_edge: Edges::Left,
l_margin: text_l_margin,
t_edge: Edges::Top,
t_margin: 0.0,
r_edge: Edges::Right,
r_margin: 0.0,
b_edge: Edges::Bottom,
b_margin: 0.0,
},
);
if is_selected {
internal_layout.add_child(
"sel_bg",
EdgeLayoutElement {
drawable: sel_bg.clone(),
l_edge: Edges::Left,
l_margin: 0.0,
t_edge: Edges::Top,
t_margin: 4.0,
r_edge: Edges::Right,
r_margin: 0.0,
b_edge: Edges::Bottom,
b_margin: 4.0,
},
);
internal_layout.add_child(
"indicator",
EdgeLayoutElement {
drawable: indicator.clone(),
l_edge: Edges::Left,
l_margin: 0.0,
t_edge: Edges::Top,
t_margin: 8.0,
r_edge: Edges::Left,
r_margin: 4.0,
b_edge: Edges::Bottom,
b_margin: 8.0,
},
);
}
let instance = Rc::new(Self {
planner: RefCell::new(None),
internal_layout: internal_layout.clone(),
text: text_comp,
sel_bg,
indicator,
is_selected: RefCell::new(is_selected),
sel_text_paint,
unsel_text_paint,
});
internal_layout.set_planner(Some(instance.clone() as Rc<dyn Layout>));
instance
}
pub fn set_selected(&self, selected: bool) {
if *self.is_selected.borrow() == selected {
return;
}
*self.is_selected.borrow_mut() = selected;
if selected {
self.internal_layout.add_child(
"sel_bg",
EdgeLayoutElement {
drawable: self.sel_bg.clone(),
l_edge: Edges::Left,
l_margin: 0.0,
t_edge: Edges::Top,
t_margin: 4.0,
r_edge: Edges::Right,
r_margin: 0.0,
b_edge: Edges::Bottom,
b_margin: 4.0,
},
);
self.internal_layout.add_child(
"indicator",
EdgeLayoutElement {
drawable: self.indicator.clone(),
l_edge: Edges::Left,
l_margin: 0.0,
t_edge: Edges::Top,
t_margin: 8.0,
r_edge: Edges::Left,
r_margin: 4.0,
b_edge: Edges::Bottom,
b_margin: 8.0,
},
);
let _ = self.internal_layout.rewrite_child(
"text",
Edges::Left,
14.0,
Edges::Top,
0.0,
Edges::Right,
0.0,
Edges::Bottom,
0.0,
);
*self.text.p.borrow_mut() = self.sel_text_paint.clone();
} else {
self.internal_layout.remove_child("sel_bg");
self.internal_layout.remove_child("indicator");
let _ = self.internal_layout.rewrite_child(
"text",
Edges::Left,
10.0,
Edges::Top,
0.0,
Edges::Right,
0.0,
Edges::Bottom,
0.0,
);
*self.text.p.borrow_mut() = self.unsel_text_paint.clone();
}
}
}
pub struct Playlist {
pub planner: RefCell<Option<Rc<dyn Layout>>>,
sh_state: RefCell<StoryBoard>,
internal_layout: Rc<EdgeLayout>,
list_layout: Rc<ListLayout>,
items: RefCell<Vec<Rc<PlaylistItem>>>,
req_w: f32,
req_h: f32,
}
impl HasPlanner for Playlist {
fn planner(&self) -> &RefCell<Option<Rc<dyn Layout>>> {
&self.planner
}
fn set_planner(&self, p: Option<Rc<dyn Layout>>) {
*self.planner.borrow_mut() = p;
}
}
impl Layout for Playlist {
fn measure_child(&self, _c: &dyn Drawable, canvas: &Canvas) -> Rect {
self.get_planned_drawing_area(canvas)
}
}
impl Drawable for Playlist {
fn draw(&self, canvas: &Canvas) -> anyhow::Result<()> {
let rect = self.get_planned_drawing_area(canvas);
let slide_percent = f64::from(&*self.sh_state.borrow()) as f32 / 100.0;
let max_slide_offset = -(rect.width() + 90.0);
let anim_x = slide_percent * max_slide_offset;
if anim_x <= max_slide_offset + 5.0 {
return Ok(());
}
canvas.save();
canvas.translate(Point::new(anim_x, 0.0));
self.internal_layout.draw(canvas)?;
canvas.restore();
Ok(())
}
fn get_horizontal_plan(&self) -> Plan {
Plan::Fit(self.req_w)
}
fn get_vertical_plan(&self) -> Plan {
Plan::Fit(self.req_h)
}
}
impl Playlist {
pub fn new(
font: &Font,
l: &[std::path::PathBuf],
selected_index: usize,
w: u32,
h: u32,
) -> Rc<Self> {
let internal_layout = EdgeLayout::new();
let list_layout = ListLayout::new(45.0, 16.0, 15.0, 15.0);
list_layout
.selected
.store(selected_index, std::sync::atomic::Ordering::Relaxed);
let mut bg_paint = Paint::default();
bg_paint
.set_anti_alias(true)
.set_color4f(Color4f::new(0.08, 0.08, 0.1, 0.80), None);
let mut border_paint = Paint::default();
border_paint
.set_anti_alias(true)
.set_style(skia_safe::paint::Style::Stroke)
.set_stroke_width(1.5)
.set_color4f(Color4f::new(1.0, 1.0, 1.0, 0.12), None);
let colors = [
Color::from_argb(255, 100, 180, 255),
Color::from_argb(255, 66, 133, 245),
];
#[allow(deprecated)]
let highlight_shader = gradient_shader::linear(
(Point::new(0.0, 0.0), Point::new(w as f32, 0.0)),
gradient_shader::GradientShaderColors::Colors(&colors),
None,
TileMode::Clamp,
None,
None,
);
let mut sel_bg_paint = Paint::default();
sel_bg_paint.set_anti_alias(true).set_alpha_f(0.15);
if let Some(s) = &highlight_shader {
sel_bg_paint.set_shader(s.clone());
}
let mut indicator_paint = Paint::default();
indicator_paint.set_anti_alias(true);
if let Some(s) = &highlight_shader {
indicator_paint.set_shader(s.clone());
}
let mut sel_text_paint = Paint::default();
sel_text_paint
.set_anti_alias(true)
.set_color4f(Color4f::new(1.0, 1.0, 1.0, 1.0), None);
let mut unsel_text_paint = Paint::default();
unsel_text_paint
.set_anti_alias(true)
.set_color4f(Color4f::new(0.7, 0.7, 0.75, 0.8), None);
let (dot_w, _) = font.measure_str("...", None);
let bg = RoundedRectangle::new(None, None, 12.0, 12.0, bg_paint);
let border = RoundedRectangle::new(None, None, 12.0, 12.0, border_paint);
let fill_all = |dr: Rc<dyn Drawable>| EdgeLayoutElement {
drawable: dr,
l_edge: Edges::Left,
l_margin: 0.0,
t_edge: Edges::Top,
t_margin: 0.0,
r_edge: Edges::Right,
r_margin: 0.0,
b_edge: Edges::Bottom,
b_margin: 0.0,
};
internal_layout.add_child("bg", fill_all(bg));
internal_layout.add_child("border", fill_all(border));
internal_layout.add_child("list", fill_all(list_layout.clone()));
let mut items = Vec::new();
for (i, path) in l.iter().enumerate() {
let file_name = path.file_name().unwrap_or_default().to_string_lossy();
let display_text = format!("{:02}. {}", i + 1, file_name);
let item = PlaylistItem::new(
&display_text,
font.clone(),
dot_w,
i == selected_index,
sel_bg_paint.clone(),
indicator_paint.clone(),
sel_text_paint.clone(),
unsel_text_paint.clone(),
);
list_layout.add_child(
&format!("item_{}", i),
ListLayoutElement {
drawable: item.clone(),
index: i,
},
);
items.push(item);
}
let instance = Rc::new(Self {
planner: RefCell::new(None),
sh_state: RefCell::new(StoryBoard::init(
100f64,
0f64,
500f64,
false,
easing::cubic_in_out,
)),
internal_layout: internal_layout.clone(),
list_layout,
items: RefCell::new(items),
req_w: w as f32,
req_h: h as f32,
});
internal_layout.set_planner(Some(instance.clone() as Rc<dyn Layout>));
instance
}
pub fn set_state(&self, selected_index: usize) {
let items = self.items.borrow();
for (i, item) in items.iter().enumerate() {
item.set_selected(i == selected_index);
}
self.list_layout
.selected
.store(selected_index, std::sync::atomic::Ordering::Relaxed);
}
pub fn toggle_sh(&self) {
let mut sh = self.sh_state.borrow_mut();
sh.reverse();
sh.restart();
}
pub fn to_layout_element(self: &Rc<Self>) -> EdgeLayoutElement {
EdgeLayoutElement {
drawable: self.clone(),
l_edge: Edges::Left,
l_margin: 40.0,
t_edge: Edges::Top,
t_margin: 311.0,
r_edge: Edges::Left,
r_margin: 40.0 + self.req_w,
b_edge: Edges::Top,
b_margin: 311.0 + self.req_h,
}
}
}