termusiclib/
xywh.rs

1use crate::config::v2::tui::{Alignment, CoverArtPosition};
2use anyhow::{Result, bail};
3use image::DynamicImage;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct AlignmentWrap(Alignment);
7
8impl AlignmentWrap {
9    const fn x(&self, absolute_x: u32, width: u32) -> u32 {
10        match self.0 {
11            Alignment::BottomRight | Alignment::TopRight => {
12                Self::get_size_substract(absolute_x, width)
13            }
14            Alignment::BottomLeft | Alignment::TopLeft => absolute_x,
15        }
16    }
17    const fn y(&self, absolute_y: u32, height: u32) -> u32 {
18        match self.0 {
19            Alignment::BottomRight | Alignment::BottomLeft => {
20                Self::get_size_substract(absolute_y, height / 2)
21            }
22            Alignment::TopRight | Alignment::TopLeft => absolute_y,
23        }
24    }
25
26    const fn get_size_substract(absolute_size: u32, size: u32) -> u32 {
27        if absolute_size > size {
28            return absolute_size - size;
29        }
30        0
31    }
32}
33
34#[derive(Clone, Debug)]
35pub struct Xywh {
36    pub x_between_1_100: u32,
37    pub y_between_1_100: u32,
38    pub width_between_1_100: u32,
39    pub x: u32,
40    pub y: u32,
41    pub width: u32,
42    pub height: u32,
43    pub align: AlignmentWrap,
44}
45
46impl From<&CoverArtPosition> for Xywh {
47    fn from(value: &CoverArtPosition) -> Self {
48        // TODO: actually apply scale
49        Self {
50            align: AlignmentWrap(value.align),
51            ..Default::default()
52        }
53    }
54}
55
56impl Default for Xywh {
57    #[allow(clippy::cast_lossless, clippy::cast_possible_truncation)]
58    fn default() -> Self {
59        let width = 20_u32;
60        let height = 20_u32;
61        let (term_width, term_height) = Self::get_terminal_size_u32();
62        let x = term_width.saturating_sub(1);
63        let y = term_height.saturating_sub(9);
64
65        Self {
66            x_between_1_100: 100,
67            y_between_1_100: 77,
68            width_between_1_100: width,
69            x,
70            y,
71            width,
72            height,
73            align: AlignmentWrap(Alignment::default()),
74        }
75    }
76}
77impl Xywh {
78    pub fn move_left(&mut self) {
79        self.x_between_1_100 = self.x_between_1_100.saturating_sub(1);
80    }
81
82    pub fn move_right(&mut self) {
83        self.x_between_1_100 += 1;
84        self.x_between_1_100 = self.x_between_1_100.min(100);
85    }
86
87    pub fn move_up(&mut self) {
88        self.y_between_1_100 = self.y_between_1_100.saturating_sub(2);
89    }
90
91    pub fn move_down(&mut self) {
92        self.y_between_1_100 += 2;
93        self.y_between_1_100 = self.y_between_1_100.min(100);
94    }
95    pub fn zoom_in(&mut self) {
96        self.width_between_1_100 += 1;
97        self.width_between_1_100 = self.width_between_1_100.min(100);
98    }
99
100    pub fn zoom_out(&mut self) {
101        self.width_between_1_100 = self.width_between_1_100.saturating_sub(1);
102    }
103
104    pub fn update_size(&self, image: &DynamicImage) -> Result<Self> {
105        let (term_width, term_height) = Self::get_terminal_size_u32();
106        let (x, y, width, height) = self.calculate_xywh(term_width, term_height, image)?;
107        Ok(Self {
108            x_between_1_100: self.x_between_1_100,
109            y_between_1_100: self.y_between_1_100,
110            width_between_1_100: self.width_between_1_100,
111            x,
112            y,
113            width,
114            height,
115            align: self.align.clone(),
116        })
117    }
118    fn calculate_xywh(
119        &self,
120        term_width: u32,
121        term_height: u32,
122        image: &DynamicImage,
123    ) -> Result<(u32, u32, u32, u32)> {
124        let width = self.get_width(term_width)?;
125        let height = Self::get_height(width, term_height, image)?;
126        let (absolute_x, absolute_y) = (
127            self.x_between_1_100 * term_width / 100,
128            self.y_between_1_100 * term_height / 100,
129        );
130        let (x, y) = (
131            self.align.x(absolute_x, width),
132            self.align.y(absolute_y, height),
133        );
134        Ok((x, y, width, height))
135    }
136
137    fn get_width(&self, term_width: u32) -> Result<u32> {
138        let width = self.width_between_1_100 * term_width / 100;
139        Self::safe_guard_width_or_height(width, term_width)
140    }
141
142    fn safe_guard_width_or_height(size: u32, size_max: u32) -> Result<u32> {
143        if size > size_max {
144            bail!("image width is too big, please reduce image width");
145        }
146        Ok(size)
147    }
148
149    fn get_height(width: u32, term_height: u32, image: &DynamicImage) -> Result<u32> {
150        let (pic_width_orig, pic_height_orig) = image::GenericImageView::dimensions(image);
151        let height = (width * pic_height_orig) / (pic_width_orig);
152        Self::safe_guard_width_or_height(height, term_height * 2)
153    }
154
155    #[must_use]
156    pub fn get_terminal_size_u32() -> (u32, u32) {
157        let (term_width, term_height) = viuer::terminal_size();
158        (u32::from(term_width), u32::from(term_height))
159    }
160}