use linearize::StaticMap;
use std::any::Any;
use std::cell::RefCell;
use std::f32;
use std::rc::Rc;
use super::anim::AnimState;
use super::{Feature, FrameStyle, MarginStyle, MarkStyle, TextClass, ThemeSize};
use crate::cast::traits::*;
use crate::config::{Config, WindowConfig};
use crate::dir::Directional;
use crate::geom::{Rect, Size, Vec2};
use crate::layout::{AlignPair, FrameRules, Margins, SizeRules, Stretch};
use crate::text::fonts::FontSelector;
crate::impl_scope! {
#[derive(Clone, Debug)]
#[impl_default]
pub struct Parameters {
pub m_inner: f32 = 1.25,
pub m_tiny: f32 = 2.4,
pub m_small: f32 = 4.35,
pub m_large: f32 = 7.4,
pub m_huge: f32 = 15.0,
pub m_text: (f32, f32) = (3.3, 0.8),
pub frame: f32 = 2.4,
pub frame_window: f32 = 4.0,
pub frame_popup: f32 = 0.0,
pub menu_frame: f32 = 2.4,
pub button_frame: f32 = 2.4,
pub button_inner: f32 = 0.0,
pub check_box: f32 = 18.0,
pub mark: f32 = 10.0,
pub grip_len: f32 = 16.0,
pub scroll_bar_size: Vec2 = Vec2(24.0, 8.0),
pub slider_size: Vec2 = Vec2(24.0, 12.0),
pub progress_bar: Vec2 = Vec2(24.0, 8.0),
pub shadow_size: Vec2 = Vec2::ZERO,
pub shadow_rel_offset: Vec2 = Vec2::ZERO,
}
}
#[derive(Clone, Debug)]
pub struct Dimensions {
pub scale: f32,
pub dpem: StaticMap<TextClass, f32>,
pub mark_line: f32,
pub diagonal_mark_line: f32,
pub min_line_len_em: f32,
pub m_inner: u16,
pub m_tiny: u16,
pub m_small: u16,
pub m_large: u16,
pub m_huge: u16,
pub m_text: (u16, u16),
pub frame: i32,
pub frame_window: i32,
pub frame_popup: i32,
pub menu_frame: i32,
pub button_frame: i32,
pub button_inner: u16,
pub check_box: i32,
pub mark: i32,
pub grip_len: i32,
pub scroll_bar: Size,
pub slider: Size,
pub progress_bar: Size,
pub shadow_a: Vec2,
pub shadow_b: Vec2,
}
impl Dimensions {
pub fn new(params: &Parameters, config: &WindowConfig) -> Self {
let scale = config.scale_factor();
let font = config.font();
let text_m0 = (params.m_text.0 * scale).cast_nearest();
let text_m1 = (params.m_text.1 * scale).cast_nearest();
let shadow_size = params.shadow_size * scale;
let shadow_offset = shadow_size * params.shadow_rel_offset;
let mark_line = (1.6 * scale).round().max(1.0);
Dimensions {
scale,
dpem: StaticMap::from_fn(|class| scale * font.get_dpem(class)),
mark_line,
diagonal_mark_line: mark_line / f32::sqrt(2.0),
min_line_len_em: 8.0,
m_inner: (params.m_inner * scale).cast_nearest(),
m_tiny: (params.m_tiny * scale).cast_nearest(),
m_small: (params.m_small * scale).cast_nearest(),
m_large: (params.m_large * scale).cast_nearest(),
m_huge: (params.m_huge * scale).cast_nearest(),
m_text: (text_m0, text_m1),
frame: (params.frame * scale).cast_nearest(),
frame_window: (params.frame_window * scale).cast_nearest(),
frame_popup: (params.frame_popup * scale).cast_nearest(),
menu_frame: (params.menu_frame * scale).cast_nearest(),
button_frame: (params.button_frame * scale).cast_nearest(),
button_inner: (params.button_inner * scale).cast_nearest(),
check_box: i32::conv_nearest(params.check_box * scale),
mark: i32::conv_nearest(params.mark * scale),
grip_len: i32::conv_nearest(params.grip_len * scale),
scroll_bar: Size::conv_nearest(params.scroll_bar_size * scale),
slider: Size::conv_nearest(params.slider_size * scale),
progress_bar: Size::conv_nearest(params.progress_bar * scale),
shadow_a: shadow_offset - shadow_size,
shadow_b: shadow_offset + shadow_size,
}
}
}
pub struct Window<D> {
pub config: Rc<RefCell<Config>>,
pub dims: Dimensions,
pub anim: AnimState<D>,
}
impl<D> Window<D> {
pub fn new(dims: &Parameters, config: &WindowConfig) -> Self {
Window {
config: config.clone_base(),
dims: Dimensions::new(dims, config),
anim: AnimState::new(&config.theme()),
}
}
pub fn update(&mut self, dims: &Parameters, config: &WindowConfig) -> bool {
let old_dims = self.dims.clone();
self.dims = Dimensions::new(dims, config);
old_dims.scale != self.dims.scale || old_dims.dpem != self.dims.dpem
}
}
impl<D: 'static> super::Window for Window<D> {
fn size(&self) -> &dyn ThemeSize {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl<D: 'static> ThemeSize for Window<D> {
fn scale_factor(&self) -> f32 {
self.dims.scale
}
fn font(&self, class: TextClass) -> FontSelector {
self.config.borrow().font.get_font_selector(class)
}
fn dpem(&self, class: TextClass) -> f32 {
self.dims.dpem[class]
}
fn wrapped_line_len(&self, _: TextClass, dpem: f32) -> (i32, i32) {
let min: i32 = (self.dims.min_line_len_em * dpem).cast_nearest();
(min, 2 * min)
}
fn min_element_size(&self) -> i32 {
(self.dims.scale * 8.0).cast_nearest()
}
fn min_scroll_size(&self, axis_is_vertical: bool, class: TextClass) -> i32 {
let factor = if axis_is_vertical {
3.0
} else {
self.dims.min_line_len_em
};
(self.dims.dpem[class] * factor).cast_ceil()
}
fn grip_len(&self) -> i32 {
self.dims.grip_len
}
fn scroll_bar_width(&self) -> i32 {
self.dims.scroll_bar.1
}
fn margins(&self, style: MarginStyle) -> Margins {
match style {
MarginStyle::None => Margins::ZERO,
MarginStyle::Inner => Margins::splat(self.dims.m_inner),
MarginStyle::Tiny => Margins::splat(self.dims.m_tiny),
MarginStyle::Small => Margins::splat(self.dims.m_small),
MarginStyle::Large => Margins::splat(self.dims.m_large),
MarginStyle::Huge => Margins::splat(self.dims.m_huge),
MarginStyle::Text => Margins::hv_splat(self.dims.m_text),
MarginStyle::Px(px) => Margins::splat(u16::conv_nearest(px * self.dims.scale)),
MarginStyle::Em(em) => {
let dpem = self.dims.dpem[TextClass::Standard];
Margins::splat(u16::conv_nearest(em * dpem))
}
}
}
fn feature(&self, feature: Feature, axis_is_vertical: bool) -> SizeRules {
let dir_is_vertical;
let mut size;
let mut ideal_mul = 3;
let m;
match feature {
Feature::Separator => {
return SizeRules::fixed(self.dims.frame);
}
Feature::Mark(style) => {
let w = match style {
MarkStyle::Chevron(dir) => match dir.is_vertical() == axis_is_vertical {
true => self.dims.mark / 2 + i32::conv_ceil(self.dims.mark_line),
false => self.dims.mark + i32::conv_ceil(self.dims.mark_line),
},
MarkStyle::X | MarkStyle::Plus | MarkStyle::Minus => {
self.dims.mark + i32::conv_ceil(self.dims.mark_line)
}
};
return SizeRules::fixed(w).with_margin(self.dims.m_tiny);
}
Feature::CheckBox | Feature::RadioBox => {
return SizeRules::fixed(self.dims.check_box).with_margin(self.dims.m_small);
}
Feature::ScrollBar(dir) => {
dir_is_vertical = dir.is_vertical();
size = self.dims.scroll_bar;
m = 0;
}
Feature::Slider(dir) => {
dir_is_vertical = dir.is_vertical();
size = self.dims.slider;
ideal_mul = 5;
m = self.dims.m_large;
}
Feature::ProgressBar(dir) => {
dir_is_vertical = dir.is_vertical();
size = self.dims.progress_bar;
m = self.dims.m_large;
}
}
let mut stretch = Stretch::High;
if dir_is_vertical != axis_is_vertical {
size = size.transpose();
ideal_mul = 1;
stretch = Stretch::None;
}
SizeRules::new(size.0, ideal_mul * size.0, stretch).with_margin(m)
}
fn align_feature(&self, feature: Feature, rect: Rect, align: AlignPair) -> Rect {
let mut ideal_size = rect.size;
match feature {
Feature::Separator => (), Feature::Mark(_) => (), Feature::CheckBox | Feature::RadioBox => {
ideal_size = Size::splat(self.dims.check_box);
}
Feature::ScrollBar(dir) => {
ideal_size.set_component(dir.flipped(), self.dims.scroll_bar.1);
}
Feature::Slider(dir) => {
ideal_size.set_component(dir.flipped(), self.dims.slider.1);
}
Feature::ProgressBar(dir) => {
ideal_size.set_component(dir.flipped(), self.dims.progress_bar.1);
}
}
align.aligned_rect(ideal_size, rect)
}
fn frame(&self, style: FrameStyle, is_vert: bool) -> FrameRules {
let outer = self.dims.m_large;
match style {
FrameStyle::None => FrameRules::ZERO,
FrameStyle::Frame => FrameRules::new_sym(self.dims.frame, 0, outer),
FrameStyle::Window => FrameRules::new_sym(self.dims.frame_window, 0, 0),
FrameStyle::Popup => FrameRules::new_sym(self.dims.frame_popup, 0, 0),
FrameStyle::MenuEntry => FrameRules::new_sym(self.dims.menu_frame, 0, 0),
FrameStyle::NavFocus => FrameRules::new_sym(0, self.dims.m_inner, 0),
FrameStyle::Button | FrameStyle::InvisibleButton | FrameStyle::Tab => {
if is_vert && style == FrameStyle::Tab {
FrameRules::new(
self.dims.button_frame,
(self.dims.button_inner, 0),
(outer, 0),
)
} else {
FrameRules::new_sym(self.dims.button_frame, self.dims.button_inner, outer)
}
}
FrameStyle::EditBox => FrameRules::new_sym(self.dims.frame, 0, outer),
}
}
}