stack: Handle scrolling properly

This commit is contained in:
Victoria Brekenfeld 2023-06-13 18:32:04 +02:00
parent f5f58b0663
commit 73d7f3779f
2 changed files with 181 additions and 40 deletions

View file

@ -1,20 +1,25 @@
use super::CosmicSurface;
use crate::{ use crate::{
shell::{focus::FocusDirection, layout::tiling::Direction, Shell}, shell::{focus::FocusDirection, layout::tiling::Direction, Shell},
state::State, state::State,
utils::iced::{IcedElement, Program}, utils::iced::{tab_text::tab_text, IcedElement, Program},
utils::prelude::SeatExt, utils::prelude::SeatExt,
wayland::handlers::screencopy::ScreencopySessions, wayland::handlers::screencopy::ScreencopySessions,
}; };
use apply::Apply; use apply::Apply;
use calloop::LoopHandle; use calloop::LoopHandle;
use cosmic::{ use cosmic::{
iced::widget as iced_widget, iced::{id::Id, widget as iced_widget},
iced_core::{alignment, renderer::BorderRadius, Background, Color, Length}, iced_core::{alignment, renderer::BorderRadius, Background, Color, Length, Size as IcedSize},
iced_runtime::Command, iced_runtime::Command,
iced_widget::rule::FillMode, iced_widget::{
rule::FillMode,
scrollable::{AbsoluteOffset, Viewport},
},
theme, widget as cosmic_widget, Element as CosmicElement, theme, widget as cosmic_widget, Element as CosmicElement,
}; };
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType; use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType;
use once_cell::sync::Lazy;
use smithay::{ use smithay::{
backend::{ backend::{
input::KeyState, input::KeyState,
@ -46,18 +51,16 @@ use std::{
}, },
}; };
use super::CosmicSurface; static SCROLLABLE_ID: Lazy<Id> = Lazy::new(|| Id::new("scrollable"));
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub struct CosmicStack(IcedElement<CosmicStackInternal>); pub struct CosmicStack(IcedElement<CosmicStackInternal>);
impl fmt::Debug for CosmicStack { impl fmt::Debug for CosmicStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.with_program(|stack| { f.debug_struct("CosmicStack")
f.debug_struct("CosmicStack") .field("internal", &self.0)
.field("internal", stack) .finish_non_exhaustive()
.finish_non_exhaustive()
})
} }
} }
@ -74,6 +77,7 @@ pub struct CosmicStackInternal {
last_location: Arc<Mutex<Option<(Point<f64, Logical>, Serial, u32)>>>, last_location: Arc<Mutex<Option<(Point<f64, Logical>, Serial, u32)>>>,
geometry: Arc<Mutex<Option<Rectangle<i32, Logical>>>>, geometry: Arc<Mutex<Option<Rectangle<i32, Logical>>>>,
mask: Arc<Mutex<Option<tiny_skia::Mask>>>, mask: Arc<Mutex<Option<tiny_skia::Mask>>>,
scrollable_offset: Arc<Mutex<Option<AbsoluteOffset>>>,
} }
impl CosmicStackInternal { impl CosmicStackInternal {
@ -88,6 +92,8 @@ impl CosmicStackInternal {
pub fn current_focus(&self) -> Focus { pub fn current_focus(&self) -> Focus {
unsafe { std::mem::transmute::<u8, Focus>(self.pointer_entered.load(Ordering::SeqCst)) } unsafe { std::mem::transmute::<u8, Focus>(self.pointer_entered.load(Ordering::SeqCst)) }
} }
//pub fn offsets_for_tabs(&self) -> Vec<
} }
const TAB_HEIGHT: i32 = 24; const TAB_HEIGHT: i32 = 24;
@ -133,6 +139,7 @@ impl CosmicStack {
last_location: Arc::new(Mutex::new(None)), last_location: Arc::new(Mutex::new(None)),
geometry: Arc::new(Mutex::new(None)), geometry: Arc::new(Mutex::new(None)),
mask: Arc::new(Mutex::new(None)), mask: Arc::new(Mutex::new(None)),
scrollable_offset: Arc::new(Mutex::new(None)),
}, },
(width, TAB_HEIGHT), (width, TAB_HEIGHT),
handle, handle,
@ -437,8 +444,11 @@ impl CosmicStack {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Message { pub enum Message {
DragStart, DragStart,
Activate(usize), Activate(usize, Option<(f32, f32, f32)>),
Close(usize), Close(usize),
ScrollForward(IcedSize),
ScrollBack,
Scrolled(Viewport),
} }
impl Program for CosmicStackInternal { impl Program for CosmicStackInternal {
@ -462,18 +472,83 @@ impl Program for CosmicStackInternal {
} }
} }
} }
Message::Activate(idx) => { Message::Activate(idx, offsets) => {
if self.windows.lock().unwrap().get(idx).is_some() { if self.windows.lock().unwrap().get(idx).is_some() {
let old = self.active.swap(idx, Ordering::SeqCst); let old = self.active.swap(idx, Ordering::SeqCst);
self.previous_keyboard.store(old, Ordering::SeqCst); self.previous_keyboard.store(old, Ordering::SeqCst);
self.previous_pointer.store(old, Ordering::SeqCst); self.previous_pointer.store(old, Ordering::SeqCst);
} }
if let Some((left_offset, right_offset, scroll_width)) = offsets {
let current_offset = self
.scrollable_offset
.lock()
.unwrap()
.unwrap_or(AbsoluteOffset::default());
let current_start = current_offset.x;
let current_end = current_start + scroll_width;
assert!((right_offset - left_offset) <= (current_end - current_start));
if (left_offset - current_start).is_sign_negative()
|| (current_end - right_offset).is_sign_negative()
{
if (left_offset - current_start).abs() < (right_offset - current_end).abs()
{
let offset = AbsoluteOffset {
x: left_offset,
y: current_offset.y,
};
*self.scrollable_offset.lock().unwrap() = Some(offset);
return iced_widget::scrollable::scroll_to::<Message>(
SCROLLABLE_ID.clone(),
offset,
);
} else {
let offset = AbsoluteOffset {
x: right_offset - scroll_width,
y: current_offset.y,
};
*self.scrollable_offset.lock().unwrap() = Some(offset);
return iced_widget::scrollable::scroll_to::<Message>(
SCROLLABLE_ID.clone(),
offset,
);
}
}
}
} }
Message::Close(idx) => { Message::Close(idx) => {
if let Some(val) = self.windows.lock().unwrap().get(idx) { if let Some(val) = self.windows.lock().unwrap().get(idx) {
val.close() val.close()
} }
} }
Message::Scrolled(viewport) => {
*self.scrollable_offset.lock().unwrap() = Some(viewport.absolute_offset());
}
Message::ScrollForward(bounds) => {
let mut offset = self
.scrollable_offset
.lock()
.unwrap()
.unwrap_or(AbsoluteOffset::default());
offset.x = (offset.x + 10.).min(bounds.width);
*self.scrollable_offset.lock().unwrap() = Some(offset);
return iced_widget::scrollable::scroll_to::<Message>(
SCROLLABLE_ID.clone(),
offset,
);
}
Message::ScrollBack => {
let mut offset = self
.scrollable_offset
.lock()
.unwrap()
.unwrap_or(AbsoluteOffset::default());
offset.x = (offset.x - 10.).max(0.0);
*self.scrollable_offset.lock().unwrap() = Some(offset);
return iced_widget::scrollable::scroll_to::<Message>(
SCROLLABLE_ID.clone(),
offset,
);
}
} }
Command::none() Command::none()
} }
@ -489,7 +564,7 @@ impl Program for CosmicStackInternal {
else { else {
return iced_widget::row(Vec::new()).into(); return iced_widget::row(Vec::new()).into();
}; };
let tab_region = width - 128 - 8; // 64 left, 64 right + last rule with padding let tab_region = width - 128 - 4; // 64 left, 64 right + last rule
let active = self.active.load(Ordering::SeqCst); let active = self.active.load(Ordering::SeqCst);
let activated = self.activated.load(Ordering::SeqCst); let activated = self.activated.load(Ordering::SeqCst);
let group_focused = self.group_focused.load(Ordering::SeqCst); let group_focused = self.group_focused.load(Ordering::SeqCst);
@ -515,7 +590,7 @@ impl Program for CosmicStackInternal {
.into()]; .into()];
const ACTIVE_TAB_WIDTH: i32 = 140; const ACTIVE_TAB_WIDTH: i32 = 140;
const MIN_TAB_WIDTH: i32 = 36; const MIN_TAB_WIDTH: i32 = 38;
let tab_width = if windows.len() == 1 { let tab_width = if windows.len() == 1 {
tab_region tab_region
} else { } else {
@ -527,8 +602,11 @@ impl Program for CosmicStackInternal {
} }
}; };
let scrolling = tab_width < MIN_TAB_WIDTH; let scrolling = tab_width < MIN_TAB_WIDTH;
let full_width = ACTIVE_TAB_WIDTH + (windows.len() as i32 - 1) * MIN_TAB_WIDTH;
let scroll_region = tab_region - 40;
let mut tabs = Vec::new(); let mut tabs = Vec::new();
let mut offset = 0;
for (i, window) in windows.iter().enumerate() { for (i, window) in windows.iter().enumerate() {
let mut tab_elements = Vec::new(); let mut tab_elements = Vec::new();
@ -593,6 +671,7 @@ impl Program for CosmicStackInternal {
}) })
.horizontal_alignment(alignment::Horizontal::Left) .horizontal_alignment(alignment::Horizontal::Left)
.vertical_alignment(alignment::Vertical::Center) .vertical_alignment(alignment::Vertical::Center)
.apply(tab_text)
.height(Length::Fill) .height(Length::Fill)
.width(text_width as u16) .width(text_width as u16)
.into(), .into(),
@ -648,9 +727,18 @@ impl Program for CosmicStackInternal {
theme::Container::Transparent theme::Container::Transparent
}) })
.apply(iced_widget::mouse_area) .apply(iced_widget::mouse_area)
.on_press(Message::Activate(i)) .on_press(Message::Activate(
i,
scrolling.then_some((
offset as f32,
(offset + tab_width + 4) as f32,
scroll_region as f32,
)),
))
.into(), .into(),
) );
offset += tab_width;
} }
let last_was_active = active == windows.len() - 1; let last_was_active = active == windows.len() - 1;
@ -686,29 +774,84 @@ impl Program for CosmicStackInternal {
.into(), .into(),
); );
let tabs = let tabs = iced_widget::row(tabs)
iced_widget::row(tabs) .apply(iced_widget::container)
.apply(iced_widget::container) .style(theme::Container::custom(|theme| {
.style(theme::Container::custom(|theme| { iced_widget::container::Appearance {
iced_widget::container::Appearance { text_color: None,
text_color: None, background: Some(cosmic::iced::Background::Color(Color::from(
background: Some(cosmic::iced::Background::Color(Color::from( theme.cosmic().palette.neutral_3,
theme.cosmic().palette.neutral_3, ))),
))), border_radius: 0.0.into(),
border_radius: 0.0.into(), border_width: 0.0,
border_width: 0.0, border_color: Color::TRANSPARENT,
border_color: Color::TRANSPARENT, }
} }))
})); .height((TAB_HEIGHT - 1) as u16);
if scrolling { if scrolling {
elements.push( elements.push(
iced_widget::Scrollable::new(tabs) iced_widget::vertical_rule(4)
.height(Length::Fill) .style(theme::Rule::custom(|theme| iced_widget::rule::Appearance {
.width(tab_region as u16) color: theme.cosmic().palette.neutral_5.into(),
width: 4,
radius: 8.,
fill_mode: FillMode::Padded(4),
}))
.into(), .into(),
) );
elements.push(
cosmic_widget::icon("go-previous-symbolic", 16)
.force_svg(true)
.style(theme::Svg::Symbolic)
.apply(iced_widget::button)
.style(theme::Button::Text)
.on_press(Message::ScrollBack)
.apply(iced_widget::container)
.height(Length::Fill)
.center_y()
.into(),
);
elements.push(
iced_widget::Scrollable::new(tabs)
.horizontal_scroll(
iced_widget::scrollable::Properties::new()
.margin(0.0)
.scroller_width(1.0)
.width(1.0),
)
.height(Length::Fill)
.width(Length::Fill)
.id(SCROLLABLE_ID.clone())
.on_scroll(Message::Scrolled)
.into(),
);
elements.push(
cosmic_widget::icon("go-next-symbolic", 16)
.force_svg(true)
.style(theme::Svg::Symbolic)
.apply(iced_widget::button)
.style(theme::Button::Text)
.on_press(Message::ScrollForward(IcedSize {
width: full_width as f32,
height: TAB_HEIGHT as f32,
}))
.apply(iced_widget::container)
.height(Length::Fill)
.center_y()
.into(),
);
elements.push(
iced_widget::vertical_rule(4)
.style(theme::Rule::custom(|theme| iced_widget::rule::Appearance {
color: theme.cosmic().palette.neutral_5.into(),
width: 4,
radius: 8.,
fill_mode: FillMode::Padded(4),
}))
.into(),
);
} else { } else {
elements.push(tabs.into()); elements.push(tabs.width(tab_region as u16).into());
} }
elements.push( elements.push(

View file

@ -50,11 +50,9 @@ pub struct CosmicWindow(IcedElement<CosmicWindowInternal>);
impl fmt::Debug for CosmicWindow { impl fmt::Debug for CosmicWindow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.with_program(|window| { f.debug_struct("CosmicWindow")
f.debug_struct("CosmicWindow") .field("internal", &self.0)
.field("internal", window) .finish_non_exhaustive()
.finish_non_exhaustive()
})
} }
} }