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::{
shell::{focus::FocusDirection, layout::tiling::Direction, Shell},
state::State,
utils::iced::{IcedElement, Program},
utils::iced::{tab_text::tab_text, IcedElement, Program},
utils::prelude::SeatExt,
wayland::handlers::screencopy::ScreencopySessions,
};
use apply::Apply;
use calloop::LoopHandle;
use cosmic::{
iced::widget as iced_widget,
iced_core::{alignment, renderer::BorderRadius, Background, Color, Length},
iced::{id::Id, widget as iced_widget},
iced_core::{alignment, renderer::BorderRadius, Background, Color, Length, Size as IcedSize},
iced_runtime::Command,
iced_widget::rule::FillMode,
iced_widget::{
rule::FillMode,
scrollable::{AbsoluteOffset, Viewport},
},
theme, widget as cosmic_widget, Element as CosmicElement,
};
use cosmic_protocols::screencopy::v1::server::zcosmic_screencopy_session_v1::InputType;
use once_cell::sync::Lazy;
use smithay::{
backend::{
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)]
pub struct CosmicStack(IcedElement<CosmicStackInternal>);
impl fmt::Debug for CosmicStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.with_program(|stack| {
f.debug_struct("CosmicStack")
.field("internal", stack)
.finish_non_exhaustive()
})
f.debug_struct("CosmicStack")
.field("internal", &self.0)
.finish_non_exhaustive()
}
}
@ -74,6 +77,7 @@ pub struct CosmicStackInternal {
last_location: Arc<Mutex<Option<(Point<f64, Logical>, Serial, u32)>>>,
geometry: Arc<Mutex<Option<Rectangle<i32, Logical>>>>,
mask: Arc<Mutex<Option<tiny_skia::Mask>>>,
scrollable_offset: Arc<Mutex<Option<AbsoluteOffset>>>,
}
impl CosmicStackInternal {
@ -88,6 +92,8 @@ impl CosmicStackInternal {
pub fn current_focus(&self) -> Focus {
unsafe { std::mem::transmute::<u8, Focus>(self.pointer_entered.load(Ordering::SeqCst)) }
}
//pub fn offsets_for_tabs(&self) -> Vec<
}
const TAB_HEIGHT: i32 = 24;
@ -133,6 +139,7 @@ impl CosmicStack {
last_location: Arc::new(Mutex::new(None)),
geometry: Arc::new(Mutex::new(None)),
mask: Arc::new(Mutex::new(None)),
scrollable_offset: Arc::new(Mutex::new(None)),
},
(width, TAB_HEIGHT),
handle,
@ -437,8 +444,11 @@ impl CosmicStack {
#[derive(Debug, Clone, Copy)]
pub enum Message {
DragStart,
Activate(usize),
Activate(usize, Option<(f32, f32, f32)>),
Close(usize),
ScrollForward(IcedSize),
ScrollBack,
Scrolled(Viewport),
}
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() {
let old = self.active.swap(idx, Ordering::SeqCst);
self.previous_keyboard.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) => {
if let Some(val) = self.windows.lock().unwrap().get(idx) {
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()
}
@ -489,7 +564,7 @@ impl Program for CosmicStackInternal {
else {
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 activated = self.activated.load(Ordering::SeqCst);
let group_focused = self.group_focused.load(Ordering::SeqCst);
@ -515,7 +590,7 @@ impl Program for CosmicStackInternal {
.into()];
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 {
tab_region
} else {
@ -527,8 +602,11 @@ impl Program for CosmicStackInternal {
}
};
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 offset = 0;
for (i, window) in windows.iter().enumerate() {
let mut tab_elements = Vec::new();
@ -593,6 +671,7 @@ impl Program for CosmicStackInternal {
})
.horizontal_alignment(alignment::Horizontal::Left)
.vertical_alignment(alignment::Vertical::Center)
.apply(tab_text)
.height(Length::Fill)
.width(text_width as u16)
.into(),
@ -648,9 +727,18 @@ impl Program for CosmicStackInternal {
theme::Container::Transparent
})
.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(),
)
);
offset += tab_width;
}
let last_was_active = active == windows.len() - 1;
@ -686,29 +774,84 @@ impl Program for CosmicStackInternal {
.into(),
);
let tabs =
iced_widget::row(tabs)
.apply(iced_widget::container)
.style(theme::Container::custom(|theme| {
iced_widget::container::Appearance {
text_color: None,
background: Some(cosmic::iced::Background::Color(Color::from(
theme.cosmic().palette.neutral_3,
))),
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
}
}));
let tabs = iced_widget::row(tabs)
.apply(iced_widget::container)
.style(theme::Container::custom(|theme| {
iced_widget::container::Appearance {
text_color: None,
background: Some(cosmic::iced::Background::Color(Color::from(
theme.cosmic().palette.neutral_3,
))),
border_radius: 0.0.into(),
border_width: 0.0,
border_color: Color::TRANSPARENT,
}
}))
.height((TAB_HEIGHT - 1) as u16);
if scrolling {
elements.push(
iced_widget::Scrollable::new(tabs)
.height(Length::Fill)
.width(tab_region as u16)
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(),
)
);
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 {
elements.push(tabs.into());
elements.push(tabs.width(tab_region as u16).into());
}
elements.push(