render: Render resize indicator

This commit is contained in:
Victoria Brekenfeld 2023-07-06 00:03:26 +02:00
parent 2004705080
commit 99f29187af
7 changed files with 407 additions and 83 deletions

View file

@ -476,19 +476,10 @@ where
}
}
state
.shell
.space_for_handle_mut(&current.0)
.ok_or(OutputNoMode)?
.update_animations(&state.event_loop_handle);
if let Some((previous, _, _)) = previous.as_ref() {
state
.shell
.space_for_handle_mut(&previous)
.ok_or(OutputNoMode)?
.update_animations(&state.event_loop_handle);
}
let overview = state.shell.overview_mode();
let (resize_mode, resize_indicator) = state.shell.resize_mode();
let resize_indicator = resize_indicator.map(|indicator| (resize_mode, indicator));
let last_active_seat = state.last_active_seat().clone();
let move_active = last_active_seat
.user_data()
@ -552,6 +543,7 @@ where
state.xwayland_state.as_mut(),
(!move_active && is_active_space).then_some(&last_active_seat),
overview.clone(),
resize_indicator.clone(),
state.config.static_conf.active_hint,
)
.map_err(|_| OutputNoMode)?
@ -598,6 +590,7 @@ where
state.xwayland_state.as_mut(),
(!move_active && is_active_space).then_some(&last_active_seat),
overview,
resize_indicator,
state.config.static_conf.active_hint,
)
.map_err(|_| OutputNoMode)?

View file

@ -54,6 +54,7 @@ pub mod stack;
pub use self::stack::CosmicStack;
pub mod window;
pub use self::window::CosmicWindow;
pub mod resize_indicator;
#[cfg(feature = "debug")]
use egui::plot::{Corner, Legend, Plot, PlotPoints, Polygon};

View file

@ -0,0 +1,223 @@
use std::sync::Mutex;
use crate::{
config::{Action, Config},
fl,
shell::{grabs::ResizeEdge, ResizeDirection},
utils::iced::{IcedElement, Program},
};
use apply::Apply;
use calloop::LoopHandle;
use cosmic::{
iced::widget::{column, container, horizontal_space, row, vertical_space},
iced_core::{Background, Color, Length},
theme,
widget::{icon, text},
};
use smithay::utils::Size;
pub type ResizeIndicator = IcedElement<ResizeIndicatorInternal>;
pub fn resize_indicator(
direction: ResizeDirection,
config: &Config,
evlh: LoopHandle<'static, crate::state::Data>,
) -> ResizeIndicator {
ResizeIndicator::new(
ResizeIndicatorInternal {
edges: Mutex::new(ResizeEdge::all()),
direction,
shortcut1: config
.static_conf
.key_bindings
.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
.static_conf
.key_bindings
.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,
)
}
pub struct ResizeIndicatorInternal {
pub edges: Mutex<ResizeEdge>,
pub direction: ResizeDirection,
pub shortcut1: String,
pub shortcut2: String,
}
impl Program for ResizeIndicatorInternal {
type Message = ();
fn view(&self) -> crate::utils::iced::Element<'_, Self::Message> {
let edges = self.edges.lock().unwrap();
column(vec![
if edges.contains(ResizeEdge::TOP) {
icon(
if self.direction == ResizeDirection::Outwards {
"go-up-symbolic"
} else {
"go-down-symbolic"
},
32,
)
.force_svg(true)
.apply(container)
.padding(2)
.style(theme::Container::custom(|theme| container::Appearance {
text_color: Some(Color::from(theme.cosmic().accent.on)),
background: Some(Background::Color(theme.cosmic().accent_color().into())),
border_radius: 18.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
}))
.width(Length::Shrink)
.apply(container)
.center_x()
.width(Length::Fill)
.into()
} else {
vertical_space(36).into()
},
row(vec![
if edges.contains(ResizeEdge::LEFT) {
icon(
if self.direction == ResizeDirection::Outwards {
"go-previous-symbolic"
} else {
"go-next-symbolic"
},
32,
)
.force_svg(true)
.apply(container)
.padding(4)
.style(theme::Container::custom(|theme| container::Appearance {
text_color: Some(Color::from(theme.cosmic().accent.on)),
background: Some(Background::Color(theme.cosmic().accent_color().into())),
border_radius: 18.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
}))
.width(Length::Shrink)
.apply(container)
.center_y()
.height(Length::Fill)
.into()
} else {
horizontal_space(36).into()
},
row(vec![
text(&self.shortcut1)
.font(cosmic::font::FONT_SEMIBOLD)
.size(14)
.into(),
text(fl!("grow-window"))
.font(cosmic::font::FONT)
.size(14)
.into(),
horizontal_space(40).into(),
text(&self.shortcut2)
.font(cosmic::font::FONT_SEMIBOLD)
.size(14)
.into(),
text(fl!("shrink-window"))
.font(cosmic::font::FONT)
.size(14)
.into(),
])
.apply(container)
.center_x()
.center_y()
.padding(16)
.apply(container)
.style(theme::Container::custom(|theme| container::Appearance {
text_color: Some(Color::from(theme.cosmic().accent.on)),
background: Some(Background::Color(theme.cosmic().accent_color().into())),
border_radius: 18.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
}))
.width(Length::Shrink)
.height(Length::Shrink)
.apply(container)
.height(Length::Fill)
.width(Length::Fill)
.center_x()
.center_y()
.into(),
if edges.contains(ResizeEdge::RIGHT) {
icon(
if self.direction == ResizeDirection::Outwards {
"go-next-symbolic"
} else {
"go-previous-symbolic"
},
32,
)
.force_svg(true)
.apply(container)
.padding(4)
.style(theme::Container::custom(|theme| container::Appearance {
text_color: Some(Color::from(theme.cosmic().accent.on)),
background: Some(Background::Color(theme.cosmic().accent_color().into())),
border_radius: 18.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
}))
.height(Length::Shrink)
.apply(container)
.center_y()
.height(Length::Fill)
.into()
} else {
horizontal_space(36).into()
},
])
.width(Length::Fill)
.height(Length::Fill)
.into(),
if edges.contains(ResizeEdge::BOTTOM) {
icon(
if self.direction == ResizeDirection::Outwards {
"go-down-symbolic"
} else {
"go-up-symbolic"
},
32,
)
.force_svg(true)
.apply(container)
.padding(4)
.style(theme::Container::custom(|theme| container::Appearance {
text_color: Some(Color::from(theme.cosmic().accent.on)),
background: Some(Background::Color(theme.cosmic().accent_color().into())),
border_radius: 18.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
}))
.width(Length::Shrink)
.apply(container)
.center_x()
.width(Length::Fill)
.into()
} else {
vertical_space(36).into()
},
])
.into()
}
}

View file

@ -16,8 +16,8 @@ use crate::{
backend::render::{element::AsGlowRenderer, IndicatorShader},
shell::{
element::{
stack::CosmicStackRenderElement, window::CosmicWindowRenderElement, CosmicMapped,
CosmicMappedRenderElement,
resize_indicator::ResizeIndicator, stack::CosmicStackRenderElement,
window::CosmicWindowRenderElement, CosmicMapped, CosmicMappedRenderElement,
},
focus::target::KeyboardFocusTarget,
grabs::ResizeEdge,
@ -421,6 +421,7 @@ impl FloatingLayout {
renderer: &mut R,
output: &Output,
focused: Option<&CosmicMapped>,
mut resize_indicator: Option<(ResizeMode, ResizeIndicator)>,
indicator_thickness: u8,
alpha: f32,
) -> Vec<CosmicMappedRenderElement<R>>
@ -435,14 +436,15 @@ impl FloatingLayout {
puffin::profile_function!();
let output_scale = output.current_scale().fractional_scale();
let output_loc = self.space.output_geometry(output).unwrap().loc;
let output_geo = self.space.output_geometry(output).unwrap();
self.space
.elements_for_output(output)
.rev()
.flat_map(|elem| {
let render_location =
self.space.element_location(elem).unwrap() - output_loc - elem.geometry().loc;
let render_location = self.space.element_location(elem).unwrap()
- output_geo.loc
- elem.geometry().loc;
let mut elements = elem.render_elements(
renderer,
render_location.to_physical_precise_round(output_scale),
@ -450,19 +452,41 @@ impl FloatingLayout {
alpha,
);
if focused == Some(elem) {
let mut indicator_geometry = Rectangle::from_loc_and_size(
self.space.element_location(elem).unwrap() - output_geo.loc,
elem.geometry().size,
);
if indicator_thickness > 0 {
let element = IndicatorShader::focus_element(
renderer,
elem.clone(),
Rectangle::from_loc_and_size(
self.space.element_location(elem).unwrap() - output_loc,
elem.geometry().size,
),
indicator_geometry,
indicator_thickness,
alpha,
);
elements.insert(0, element.into());
}
if let Some((mode, resize)) = resize_indicator.as_mut() {
indicator_geometry.loc -= (18, 18).into();
indicator_geometry.size += (36, 36).into();
resize.resize(indicator_geometry.size);
resize.output_enter(output, output_geo);
elements = resize
.render_elements::<CosmicWindowRenderElement<R>>(
renderer,
indicator_geometry
.loc
.to_physical_precise_round(output_scale),
output_scale.into(),
alpha * mode.alpha().unwrap_or(1.0),
)
.into_iter()
.map(CosmicMappedRenderElement::Window)
.chain(elements.into_iter())
.collect();
}
}
elements
})

View file

@ -4,6 +4,7 @@ use crate::{
backend::render::{element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, GROUP_COLOR},
shell::{
element::{
resize_indicator::ResizeIndicator,
stack::{CosmicStackRenderElement, MoveResult as StackMoveResult},
window::CosmicWindowRenderElement,
CosmicMapped, CosmicMappedRenderElement, CosmicStack, CosmicWindow,
@ -1915,6 +1916,7 @@ impl TilingLayout {
seat: Option<&Seat<State>>,
non_exclusive_zone: Rectangle<i32, Logical>,
overview: OverviewMode,
resize_indicator: Option<(ResizeMode, ResizeIndicator)>,
indicator_thickness: u8,
) -> Result<Vec<CosmicMappedRenderElement<R>>, OutputNotMapped>
where
@ -1955,26 +1957,7 @@ impl TilingLayout {
} else {
1.0
};
let draw_groups = match overview {
OverviewMode::Started(_, start) => {
let percentage = (Instant::now().duration_since(start).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32)
.min(1.0);
Some(Ease::Cubic(Cubic::Out).tween(percentage))
}
OverviewMode::Ended(end) => {
let percentage = (1.0
- Instant::now().duration_since(end).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32)
.max(0.0);
if percentage > 0.0 {
Some(Ease::Cubic(Cubic::Out).tween(percentage))
} else {
None
}
}
OverviewMode::None => None,
};
let draw_groups = overview.alpha();
let mut elements = Vec::new();
@ -2032,7 +2015,7 @@ impl TilingLayout {
geometries,
old_geometries,
seat,
output_scale,
output,
percentage,
if let Some(transition) = draw_groups {
let diff = (4u8.abs_diff(indicator_thickness) as f32 * transition).round() as u8;
@ -2044,6 +2027,7 @@ impl TilingLayout {
} else {
indicator_thickness
},
resize_indicator,
));
// tiling hints
@ -2425,9 +2409,10 @@ fn render_new_tree<R>(
geometries: Option<HashMap<NodeId, Rectangle<i32, Logical>>>,
old_geometries: Option<HashMap<NodeId, Rectangle<i32, Logical>>>,
seat: Option<&Seat<State>>,
output_scale: f64,
output: &Output,
percentage: f32,
indicator_thickness: u8,
mut resize_indicator: Option<(ResizeMode, ResizeIndicator)>,
) -> Vec<CosmicMappedRenderElement<R>>
where
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
@ -2452,11 +2437,16 @@ where
.map(|(id, _)| id);
let mut group_backdrop = None;
let mut indicator = None;
let mut resize_elements = None;
let output_geo = output.geometry();
let output_scale = output.current_scale().fractional_scale();
if let Some(root) = target_tree.root_node_id() {
let old_geometries = old_geometries.unwrap_or_default();
let geometries = geometries.unwrap_or_default();
let mut elements: Vec<CosmicMappedRenderElement<R>> = target_tree
let elements: Vec<CosmicMappedRenderElement<R>> = target_tree
.traverse_pre_order_ids(root)
.unwrap()
.flat_map(|node_id| {
@ -2546,7 +2536,7 @@ where
let mut elements = Vec::new();
if focused == Some(node_id) {
if focused.as_ref() == Some(&node_id) {
if indicator_thickness > 0 || data.is_group() {
let mut geo = geo.clone();
if data.is_group() {
@ -2554,25 +2544,56 @@ where
geo.loc += (outer_gap, outer_gap).into();
geo.size -= (outer_gap * 2, outer_gap * 2).into();
group_backdrop = Some(
BackdropShader::element(
renderer,
match data {
Data::Group { alive, .. } => {
Key::Group(Arc::downgrade(alive))
}
_ => unreachable!(),
},
geo,
8.,
0.4,
GROUP_COLOR,
)
.into(),
);
group_backdrop = Some(BackdropShader::element(
renderer,
match data {
Data::Group { alive, .. } => Key::Group(Arc::downgrade(alive)),
_ => unreachable!(),
},
geo,
8.,
0.4,
GROUP_COLOR,
));
}
let element = IndicatorShader::focus_element(
if let Some((mode, resize)) = resize_indicator.as_mut() {
let mut geo = geo.clone();
geo.loc -= (18, 18).into();
geo.size += (36, 36).into();
resize.resize(geo.size);
resize.output_enter(output, output_geo);
let possible_edges =
TilingLayout::possible_resizes(target_tree, node_id);
if !possible_edges.is_empty() {
if resize.with_program(|internal| {
let mut edges = internal.edges.lock().unwrap();
if *edges != possible_edges {
*edges = possible_edges;
true
} else {
false
}
}) {
resize.force_update();
}
resize_elements = Some(
resize
.render_elements::<CosmicWindowRenderElement<R>>(
renderer,
geo.loc.to_physical_precise_round(output_scale),
output_scale.into(),
alpha * mode.alpha().unwrap_or(1.0),
)
.into_iter()
.map(CosmicMappedRenderElement::from)
.collect::<Vec<_>>(),
);
}
}
indicator = Some(IndicatorShader::focus_element(
renderer,
match data {
Data::Mapped { mapped, .. } => mapped.clone().into(),
@ -2585,8 +2606,7 @@ where
indicator_thickness
},
1.0,
);
elements.push(element.into());
));
}
}
@ -2654,8 +2674,14 @@ where
elements
})
.collect();
elements.extend(group_backdrop);
elements
resize_elements
.into_iter()
.flatten()
.chain(indicator.into_iter().map(Into::into))
.chain(elements)
.chain(group_backdrop.into_iter().map(Into::into))
.collect()
} else {
Vec::new()
}

View file

@ -4,12 +4,13 @@ use std::{
cell::RefCell,
collections::HashMap,
sync::atomic::{AtomicBool, Ordering},
time::Instant,
time::{Duration, Instant},
};
use tracing::warn;
use wayland_backend::server::ClientId;
use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState;
use cosmic_time::{Cubic, Ease, Tween};
use smithay::{
desktop::{
layer_map_for_output, space::SpaceElement, LayerSurface, PopupManager, WindowSurfaceType,
@ -63,10 +64,12 @@ use self::{
grabs::ResizeEdge,
layout::{
floating::{FloatingLayout, ResizeState},
tiling::{Direction, TilingLayout, ANIMATION_DURATION},
tiling::{Direction, TilingLayout},
},
};
const ANIMATION_DURATION: Duration = Duration::from_millis(200);
#[derive(Debug, Clone)]
pub enum OverviewMode {
None,
@ -74,6 +77,31 @@ pub enum OverviewMode {
Ended(Instant),
}
impl OverviewMode {
pub fn alpha(&self) -> Option<f32> {
match self {
OverviewMode::Started(_, start) => {
let percentage = (Instant::now().duration_since(*start).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32)
.min(1.0);
Some(Ease::Cubic(Cubic::Out).tween(percentage))
}
OverviewMode::Ended(end) => {
let percentage = (1.0
- Instant::now().duration_since(*end).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32)
.max(0.0);
if percentage > 0.0 {
Some(Ease::Cubic(Cubic::Out).tween(percentage))
} else {
None
}
}
OverviewMode::None => None,
}
}
}
#[derive(Debug, Clone, Copy, serde::Deserialize, PartialEq, Eq, Hash)]
pub enum ResizeDirection {
Inwards,
@ -87,6 +115,31 @@ pub enum ResizeMode {
Ended(Instant, ResizeDirection),
}
impl ResizeMode {
pub fn alpha(&self) -> Option<f32> {
match self {
ResizeMode::Started(_, start, _) => {
let percentage = (Instant::now().duration_since(*start).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32)
.min(1.0);
Some(Ease::Cubic(Cubic::Out).tween(percentage))
}
ResizeMode::Ended(end, _) => {
let percentage = (1.0
- Instant::now().duration_since(*end).as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32)
.max(0.0);
if percentage > 0.0 {
Some(Ease::Cubic(Cubic::Out).tween(percentage))
} else {
None
}
}
ResizeMode::None => None,
}
}
}
pub struct Shell {
pub popups: PopupManager,
pub outputs: Vec<Output>,
@ -114,6 +167,7 @@ pub struct Shell {
usize,
Output,
)>,
resize_indicator: Option<ResizeIndicator>,
}
#[derive(Debug)]
@ -574,6 +628,7 @@ impl Shell {
overview_mode: OverviewMode::None,
resize_mode: ResizeMode::None,
resize_state: None,
resize_indicator: None,
}
}
@ -1177,7 +1232,12 @@ impl Shell {
self.overview_mode.clone()
}
pub fn set_resize_mode(&mut self, enabled: Option<(KeyPattern, ResizeDirection)>) {
pub fn set_resize_mode(
&mut self,
enabled: Option<(KeyPattern, ResizeDirection)>,
config: &Config,
evlh: LoopHandle<'static, crate::state::Data>,
) {
if let Some((pattern, direction)) = enabled {
if let ResizeMode::Started(old_pattern, _, old_direction) = &mut self.resize_mode {
*old_pattern = pattern;
@ -1185,6 +1245,7 @@ impl Shell {
} else {
self.resize_mode = ResizeMode::Started(pattern, Instant::now(), direction);
}
self.resize_indicator = Some(resize_indicator(direction, config, evlh));
} else {
if let ResizeMode::Started(_, _, direction) = &self.resize_mode {
self.resize_mode = ResizeMode::Ended(Instant::now(), *direction);
@ -1192,25 +1253,15 @@ impl Shell {
}
}
pub fn resize_mode(&mut self) -> ResizeMode {
pub fn resize_mode(&mut self) -> (ResizeMode, Option<ResizeIndicator>) {
if let ResizeMode::Ended(timestamp, _) = self.resize_mode {
if Instant::now().duration_since(timestamp) > ANIMATION_DURATION {
self.resize_mode = ResizeMode::None;
self.resize_indicator = None;
}
}
self.resize_mode.clone()
}
pub fn resize_active_window(
&mut self,
seat: &Seat<State>,
direction: ResizeDirection,
edge: ResizeEdge,
) {
self.workspaces
.active_mut(&seat.active_output())
.resize(seat, direction, edge);
(self.resize_mode.clone(), self.resize_indicator.clone())
}
pub fn refresh(&mut self) {

View file

@ -47,7 +47,10 @@ use tracing::warn;
use wayland_backend::server::ClientId;
use super::{
element::{stack::CosmicStackRenderElement, window::CosmicWindowRenderElement, CosmicMapped},
element::{
resize_indicator::ResizeIndicator, stack::CosmicStackRenderElement,
window::CosmicWindowRenderElement, CosmicMapped,
},
focus::{target::KeyboardFocusTarget, FocusStack, FocusStackMut},
grabs::{ResizeEdge, ResizeGrab},
CosmicMappedRenderElement, CosmicSurface, ResizeDirection, ResizeMode,
@ -500,6 +503,7 @@ impl Workspace {
xwm_state: Option<&'a mut XWaylandState>,
draw_focus_indicator: Option<&Seat<State>>,
overview: OverviewMode,
resize_indicator: Option<(ResizeMode, ResizeIndicator)>,
indicator_thickness: u8,
) -> Result<Vec<WorkspaceRenderElement<R>>, OutputNotMapped>
where
@ -606,6 +610,7 @@ impl Workspace {
renderer,
output,
focused.as_ref(),
resize_indicator.clone(),
indicator_thickness,
alpha,
)
@ -622,6 +627,7 @@ impl Workspace {
draw_focus_indicator,
layer_map.non_exclusive_zone(),
overview.clone(),
resize_indicator,
indicator_thickness,
)?
.into_iter()