use std::sync::{Arc, Mutex}; use crate::{ backend::render::element::AsGlowRenderer, config::Config, fl, shell::grabs::ResizeEdge, utils::iced::{IcedElement, IcedRenderElement, Program}, }; use calloop::LoopHandle; use cosmic::{ Apply, iced::{ Alignment, core::{Background, Border, Color, Length}, widget::{container, row, space}, }, theme, widget::{icon::from_name, text}, }; use cosmic_settings_config::shortcuts::action::{Action, ResizeDirection}; use smithay::{ backend::renderer::ImportMem, desktop::space::SpaceElement, output::Output, utils::{Logical, Physical, Point, Rectangle, Scale, Size}, }; #[derive(Debug, Clone)] pub struct ResizeIndicator { edges: ResizeEdge, direction: Arc>, size: Size, center_elem: IcedElement, left_elem: IcedElement, top_elem: IcedElement, right_elem: IcedElement, down_elem: IcedElement, } const ARROW_SIZE: i32 = 36; impl ResizeIndicator { pub fn new( direction: ResizeDirection, config: &Config, evlh: LoopHandle<'static, crate::state::State>, mut theme: cosmic::Theme, ) -> ResizeIndicator { theme.transparent = theme.cosmic().frosted_system_interface; let direction = Arc::new(Mutex::new(direction)); ResizeIndicator { edges: ResizeEdge::all(), direction: direction.clone(), size: Size::default(), center_elem: IcedElement::new( ResizeIndicatorInternal { shortcut1: config .shortcuts .iter() .find_map(|(pattern, action)| { (*action == Action::Resizing(ResizeDirection::Outwards)) .then_some(pattern) }) .map(|pattern| format!("{}: ", pattern.to_string())) .unwrap_or_else(|| crate::fl!("unknown-keybinding")), shortcut2: config .shortcuts .iter() .find_map(|(pattern, action)| { (*action == Action::Resizing(ResizeDirection::Inwards)) .then_some(pattern) }) .map(|pattern| format!("{}: ", pattern.to_string())) .unwrap_or_else(|| crate::fl!("unknown-keybinding")), }, Size::from((1, 1)), evlh.clone(), theme.clone(), ), left_elem: IcedElement::new( ResizeIndicatorArrow { direction: direction.clone(), icon_outwards: "go-previous-symbolic", icon_inwards: "go-next-symbolic", }, Size::from((ARROW_SIZE, ARROW_SIZE)), evlh.clone(), theme.clone(), ), top_elem: IcedElement::new( ResizeIndicatorArrow { direction: direction.clone(), icon_outwards: "go-up-symbolic", icon_inwards: "go-down-symbolic", }, Size::from((ARROW_SIZE, ARROW_SIZE)), evlh.clone(), theme.clone(), ), right_elem: IcedElement::new( ResizeIndicatorArrow { direction: direction.clone(), icon_outwards: "go-next-symbolic", icon_inwards: "go-previous-symbolic", }, Size::from((ARROW_SIZE, ARROW_SIZE)), evlh.clone(), theme.clone(), ), down_elem: IcedElement::new( ResizeIndicatorArrow { direction, icon_outwards: "go-down-symbolic", icon_inwards: "go-up-symbolic", }, Size::from((ARROW_SIZE, ARROW_SIZE)), evlh.clone(), theme.clone(), ), } } pub fn resize(&mut self, size: Size) { let minimum = self.center_elem.minimum_size(); let new_size = Size::::new(size.w.min(minimum.w), size.h.min(minimum.h)); self.center_elem.resize(new_size); self.size = size; } pub fn push_render_elements( &self, renderer: &mut R, location: Point, scale: Scale, alpha: f32, push_above: &mut dyn FnMut(IcedRenderElement), mut push_below: Option<&mut dyn FnMut(IcedRenderElement)>, ) where R: AsGlowRenderer + ImportMem, R::TextureId: Send + Clone + 'static, { let elem_size = self.center_elem.current_size(); let center_location = Point::new( self.size.w.saturating_sub(elem_size.w) / 2, self.size.h.saturating_sub(elem_size.h) / 2, ); let left_location = Point::new(0, self.size.h.saturating_sub(ARROW_SIZE) / 2); let top_location = Point::new(self.size.w.saturating_sub(ARROW_SIZE) / 2, 0); let right_location = Point::new( self.size.w.saturating_sub(ARROW_SIZE), self.size.h.saturating_sub(ARROW_SIZE) / 2, ); let down_location = Point::new( self.size.w.saturating_sub(ARROW_SIZE) / 2, self.size.h.saturating_sub(ARROW_SIZE), ); let radii = self .center_elem .with_theme(|theme| theme.cosmic().radius_s()) .map(|x| x.round() as u8); self.center_elem.push_render_elements( renderer, location + center_location.to_physical_precise_round(scale), scale, alpha, radii, push_above, if let Some(push_below) = push_below.as_mut() { Some(&mut **push_below) } else { None }, ); let mut render = move |elem: &IcedElement, loc: Point| { elem.push_render_elements( renderer, location + loc.to_physical_precise_round(scale), scale, alpha, radii, push_above, if let Some(push_below) = push_below.as_mut() { Some(&mut **push_below) } else { None }, ); }; if self.edges.contains(ResizeEdge::LEFT) { render(&self.left_elem, left_location); } if self.edges.contains(ResizeEdge::TOP) { render(&self.top_elem, top_location); } if self.edges.contains(ResizeEdge::RIGHT) { render(&self.right_elem, right_location); } if self.edges.contains(ResizeEdge::BOTTOM) { render(&self.down_elem, down_location); } } pub fn set_edges(&mut self, edges: ResizeEdge) { self.edges = edges; } pub fn set_direction(&self, direction: ResizeDirection) { let mut dir_ref = self.direction.lock().unwrap(); if *dir_ref != direction { *dir_ref = direction; self.left_elem.force_redraw(); self.top_elem.force_redraw(); self.right_elem.force_redraw(); self.down_elem.force_redraw(); } } pub fn output_enter(&self, output: &Output) { self.center_elem .output_enter(output, Rectangle::default() /*unused*/); self.left_elem .output_enter(output, Rectangle::default() /*unused*/); self.top_elem .output_enter(output, Rectangle::default() /*unused*/); self.right_elem .output_enter(output, Rectangle::default() /*unused*/); self.down_elem .output_enter(output, Rectangle::default() /*unused*/); } pub fn output_leave(&self, output: &Output) { self.center_elem.output_leave(output); self.left_elem.output_leave(output); self.top_elem.output_leave(output); self.right_elem.output_leave(output); self.down_elem.output_leave(output); } } pub struct ResizeIndicatorInternal { shortcut1: String, shortcut2: String, } impl Program for ResizeIndicatorInternal { type Message = (); fn view(&self) -> cosmic::Element<'_, Self::Message> { row(vec![ text::heading(&self.shortcut1).into(), text::body(fl!("grow-window")).into(), space::horizontal().width(40).into(), text::heading(&self.shortcut2).into(), text::body(fl!("shrink-window")).into(), ]) .apply(container) .align_x(Alignment::Center) .align_y(Alignment::Center) .padding(16) .apply(container) .class(theme::Container::custom(|theme| { let mut background = theme.cosmic().accent_color(); if theme.transparent { background.alpha = theme .cosmic() .alpha_map .blurred_alpha(theme.cosmic().frosted); } container::Style { snap: true, icon_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)), background: Some(Background::Color(background.into())), border: Border { radius: theme.cosmic().radius_s().into(), width: 0.0, color: Color::TRANSPARENT, }, shadow: Default::default(), } })) .width(Length::Shrink) .height(Length::Shrink) .into() } } pub struct ResizeIndicatorArrow { direction: Arc>, icon_outwards: &'static str, icon_inwards: &'static str, } impl Program for ResizeIndicatorArrow { type Message = (); fn view(&self) -> cosmic::Element<'_, Self::Message> { from_name( if *self.direction.lock().unwrap() == ResizeDirection::Outwards { self.icon_outwards } else { self.icon_inwards }, ) .size(20) .prefer_svg(true) .apply(container) .padding(8) .class(theme::Container::custom(|theme| { let mut background = theme.cosmic().accent_color(); if theme.transparent { background.alpha = theme .cosmic() .alpha_map .blurred_alpha(theme.cosmic().frosted); } container::Style { snap: true, icon_color: Some(Color::from(theme.cosmic().accent.on)), text_color: Some(Color::from(theme.cosmic().accent.on)), background: Some(Background::Color(background.into())), border: Border { radius: theme.cosmic().radius_s().into(), width: 0.0, color: Color::TRANSPARENT, }, shadow: Default::default(), } })) .width(Length::Shrink) .into() } }