342 lines
11 KiB
Rust
342 lines
11 KiB
Rust
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<Mutex<ResizeDirection>>,
|
|
size: Size<i32, Logical>,
|
|
|
|
center_elem: IcedElement<ResizeIndicatorInternal>,
|
|
left_elem: IcedElement<ResizeIndicatorArrow>,
|
|
top_elem: IcedElement<ResizeIndicatorArrow>,
|
|
right_elem: IcedElement<ResizeIndicatorArrow>,
|
|
down_elem: IcedElement<ResizeIndicatorArrow>,
|
|
}
|
|
|
|
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<i32, Logical>) {
|
|
let minimum = self.center_elem.minimum_size();
|
|
let new_size = Size::<i32, Logical>::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<R>(
|
|
&self,
|
|
renderer: &mut R,
|
|
location: Point<i32, Physical>,
|
|
scale: Scale<f64>,
|
|
alpha: f32,
|
|
push_above: &mut dyn FnMut(IcedRenderElement<R>),
|
|
mut push_below: Option<&mut dyn FnMut(IcedRenderElement<R>)>,
|
|
) 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<ResizeIndicatorArrow>,
|
|
loc: Point<i32, Logical>| {
|
|
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<Mutex<ResizeDirection>>,
|
|
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()
|
|
}
|
|
}
|