2023-06-22 20:58:39 +02:00
|
|
|
use super::tab::{Tab, TabBackgroundTheme, TabMessage, TabRuleTheme, MIN_ACTIVE_TAB_WIDTH};
|
|
|
|
|
use cosmic::{
|
|
|
|
|
font::Font,
|
|
|
|
|
iced::{id::Id, widget, Element},
|
|
|
|
|
iced_core::{
|
|
|
|
|
event,
|
|
|
|
|
layout::{Layout, Limits, Node},
|
|
|
|
|
mouse, overlay, renderer,
|
|
|
|
|
widget::{
|
|
|
|
|
operation::{
|
|
|
|
|
scrollable::{AbsoluteOffset, RelativeOffset},
|
|
|
|
|
Operation, OperationOutputWrapper, Scrollable,
|
|
|
|
|
},
|
|
|
|
|
text::StyleSheet as TextStyleSheet,
|
|
|
|
|
tree::{self, Tree},
|
|
|
|
|
Widget,
|
|
|
|
|
},
|
|
|
|
|
Background, Clipboard, Color, Length, Point, Rectangle, Shell, Size, Vector,
|
|
|
|
|
},
|
|
|
|
|
iced_style::{
|
|
|
|
|
button::StyleSheet as ButtonStyleSheet, container::StyleSheet as ContainerStyleSheet,
|
|
|
|
|
rule::StyleSheet as RuleStyleSheet,
|
|
|
|
|
},
|
|
|
|
|
iced_widget::container::draw_background,
|
|
|
|
|
theme,
|
2023-10-02 19:37:23 +02:00
|
|
|
widget::{icon::from_name, Icon},
|
|
|
|
|
Apply,
|
2023-06-22 20:58:39 +02:00
|
|
|
};
|
2023-07-11 17:12:56 +02:00
|
|
|
use keyframe::{
|
|
|
|
|
ease,
|
|
|
|
|
functions::{EaseInOutCubic, EaseOutCubic},
|
|
|
|
|
};
|
2023-06-26 21:05:31 +02:00
|
|
|
use std::{
|
|
|
|
|
collections::{HashMap, HashSet, VecDeque},
|
|
|
|
|
time::{Duration, Instant},
|
|
|
|
|
};
|
2023-06-22 20:58:39 +02:00
|
|
|
|
|
|
|
|
pub struct Tabs<'a, Message, Renderer>
|
|
|
|
|
where
|
|
|
|
|
Renderer: cosmic::iced_core::Renderer,
|
|
|
|
|
Renderer::Theme: RuleStyleSheet,
|
|
|
|
|
{
|
|
|
|
|
elements: Vec<Element<'a, Message, Renderer>>,
|
|
|
|
|
id: Option<Id>,
|
|
|
|
|
height: Length,
|
|
|
|
|
width: Length,
|
|
|
|
|
group_focused: bool,
|
|
|
|
|
scroll_to: Option<usize>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-23 16:09:01 +02:00
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
struct ScrollAnimationState {
|
|
|
|
|
start_time: Instant,
|
|
|
|
|
start: Offset,
|
|
|
|
|
end: Offset,
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 21:05:31 +02:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
struct TabAnimationState {
|
|
|
|
|
previous_bounds: HashMap<Id, Rectangle>,
|
|
|
|
|
next_bounds: HashMap<Id, Rectangle>,
|
|
|
|
|
start_time: Instant,
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 20:58:39 +02:00
|
|
|
/// The local state of [`Tabs`].
|
2023-06-26 21:05:31 +02:00
|
|
|
#[derive(Debug, Clone)]
|
2023-06-22 20:58:39 +02:00
|
|
|
pub struct State {
|
|
|
|
|
offset_x: Offset,
|
2023-06-23 16:09:01 +02:00
|
|
|
scroll_animation: Option<ScrollAnimationState>,
|
2023-06-22 20:58:39 +02:00
|
|
|
scroll_to: Option<usize>,
|
2023-06-26 21:05:31 +02:00
|
|
|
last_state: Option<HashMap<Id, Rectangle>>,
|
|
|
|
|
tab_animations: VecDeque<TabAnimationState>,
|
2023-06-22 20:58:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Scrollable for State {
|
|
|
|
|
fn snap_to(&mut self, offset: RelativeOffset) {
|
|
|
|
|
self.offset_x = Offset::Relative(offset.x.clamp(0.0, 1.0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn scroll_to(&mut self, offset: AbsoluteOffset) {
|
2023-06-23 16:09:01 +02:00
|
|
|
let new_offset = Offset::Absolute(offset.x.max(0.0));
|
|
|
|
|
self.scroll_animation = Some(ScrollAnimationState {
|
|
|
|
|
start_time: Instant::now(),
|
|
|
|
|
start: self.offset_x,
|
|
|
|
|
end: new_offset,
|
|
|
|
|
});
|
|
|
|
|
self.offset_x = new_offset;
|
2023-06-22 20:58:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for State {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
State {
|
|
|
|
|
offset_x: Offset::Absolute(0.),
|
2023-06-23 16:09:01 +02:00
|
|
|
scroll_animation: None,
|
2023-06-22 20:58:39 +02:00
|
|
|
scroll_to: None,
|
2023-06-26 21:05:31 +02:00
|
|
|
last_state: None,
|
|
|
|
|
tab_animations: VecDeque::new(),
|
2023-06-22 20:58:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
enum Offset {
|
|
|
|
|
Absolute(f32),
|
|
|
|
|
Relative(f32),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Offset {
|
|
|
|
|
fn absolute(self, viewport: f32, content: f32) -> f32 {
|
|
|
|
|
match self {
|
|
|
|
|
Offset::Absolute(absolute) => absolute.min((content - viewport).max(0.0)),
|
|
|
|
|
Offset::Relative(percentage) => ((content - viewport) * percentage).max(0.0),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 21:05:31 +02:00
|
|
|
const SCROLL_ANIMATION_DURATION: Duration = Duration::from_millis(200);
|
|
|
|
|
const TAB_ANIMATION_DURATION: Duration = Duration::from_millis(150);
|
2023-06-23 16:09:01 +02:00
|
|
|
|
2023-06-22 20:58:39 +02:00
|
|
|
impl<'a, Message, Renderer> Tabs<'a, Message, Renderer>
|
|
|
|
|
where
|
|
|
|
|
Renderer: cosmic::iced_core::Renderer + 'a,
|
|
|
|
|
Renderer: cosmic::iced_core::text::Renderer<Font = Font>,
|
2023-10-02 19:37:23 +02:00
|
|
|
Renderer::Theme: ButtonStyleSheet<Style = theme::iced::Button>,
|
2023-06-22 20:58:39 +02:00
|
|
|
Renderer::Theme: ContainerStyleSheet<Style = theme::Container>,
|
|
|
|
|
Renderer::Theme: RuleStyleSheet<Style = theme::Rule>,
|
|
|
|
|
Renderer::Theme: TextStyleSheet,
|
|
|
|
|
Message: TabMessage + 'a,
|
|
|
|
|
widget::Button<'a, Message, Renderer>: Into<Element<'a, Message, Renderer>>,
|
|
|
|
|
widget::Container<'a, Message, Renderer>: Into<Element<'a, Message, Renderer>>,
|
2023-10-02 19:37:23 +02:00
|
|
|
Icon: Into<Element<'a, Message, Renderer>>,
|
2023-06-22 20:58:39 +02:00
|
|
|
{
|
|
|
|
|
pub fn new(
|
2023-10-02 19:37:23 +02:00
|
|
|
tabs: impl IntoIterator<Item = Tab<Message>>,
|
2023-06-22 20:58:39 +02:00
|
|
|
active: usize,
|
|
|
|
|
activated: bool,
|
|
|
|
|
group_focused: bool,
|
|
|
|
|
) -> Self {
|
|
|
|
|
let mut tabs = tabs
|
|
|
|
|
.into_iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.map(|(i, tab)| {
|
|
|
|
|
let rule = if activated {
|
|
|
|
|
TabRuleTheme::ActiveActivated
|
|
|
|
|
} else {
|
|
|
|
|
TabRuleTheme::ActiveDeactivated
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let tab = if i == active {
|
|
|
|
|
tab.rule_style(rule)
|
|
|
|
|
.background_style(if activated {
|
|
|
|
|
TabBackgroundTheme::ActiveActivated
|
|
|
|
|
} else {
|
|
|
|
|
TabBackgroundTheme::ActiveDeactivated
|
|
|
|
|
})
|
|
|
|
|
.font(cosmic::font::FONT_SEMIBOLD)
|
|
|
|
|
.active()
|
|
|
|
|
} else if i.checked_sub(1) == Some(active) {
|
|
|
|
|
tab.rule_style(rule).non_active()
|
|
|
|
|
} else {
|
|
|
|
|
tab.non_active()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Element::new(tab.internal(i))
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
tabs.push(
|
|
|
|
|
widget::vertical_rule(4)
|
|
|
|
|
.style(if tabs.len() - 1 == active {
|
|
|
|
|
if activated {
|
|
|
|
|
TabRuleTheme::ActiveActivated
|
|
|
|
|
} else {
|
|
|
|
|
TabRuleTheme::ActiveDeactivated
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
TabRuleTheme::Default
|
|
|
|
|
})
|
|
|
|
|
.into(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Tabs {
|
|
|
|
|
elements: vec![
|
|
|
|
|
widget::vertical_rule(4)
|
|
|
|
|
.style(if group_focused {
|
|
|
|
|
TabRuleTheme::ActiveActivated
|
|
|
|
|
} else {
|
|
|
|
|
TabRuleTheme::Default
|
|
|
|
|
})
|
|
|
|
|
.into(),
|
2023-10-02 19:37:23 +02:00
|
|
|
from_name("go-previous-symbolic")
|
|
|
|
|
.size(16)
|
|
|
|
|
.prefer_svg(true)
|
|
|
|
|
.icon()
|
2023-06-22 20:58:39 +02:00
|
|
|
.apply(widget::button)
|
2023-10-02 19:37:23 +02:00
|
|
|
.style(theme::iced::Button::Text)
|
2023-06-22 20:58:39 +02:00
|
|
|
.on_press(Message::scroll_back())
|
|
|
|
|
.into(),
|
|
|
|
|
]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.chain(tabs)
|
|
|
|
|
.chain(vec![
|
2023-10-02 19:37:23 +02:00
|
|
|
from_name("go-next-symbolic")
|
|
|
|
|
.size(16)
|
|
|
|
|
.prefer_svg(true)
|
|
|
|
|
.icon()
|
2023-06-22 20:58:39 +02:00
|
|
|
.apply(widget::button)
|
2023-10-02 19:37:23 +02:00
|
|
|
.style(theme::iced::Button::Text)
|
2023-06-22 20:58:39 +02:00
|
|
|
.on_press(Message::scroll_further())
|
|
|
|
|
.into(),
|
|
|
|
|
widget::vertical_rule(4)
|
|
|
|
|
.style(if group_focused {
|
|
|
|
|
TabRuleTheme::ActiveActivated
|
|
|
|
|
} else {
|
|
|
|
|
TabRuleTheme::Default
|
|
|
|
|
})
|
|
|
|
|
.into(),
|
|
|
|
|
])
|
|
|
|
|
.collect(),
|
|
|
|
|
id: None,
|
|
|
|
|
width: Length::Fill,
|
|
|
|
|
height: Length::Shrink,
|
|
|
|
|
group_focused,
|
|
|
|
|
scroll_to: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn id(mut self, id: impl Into<Id>) -> Self {
|
|
|
|
|
self.id = Some(id.into());
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
|
|
|
|
self.width = width.into();
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn height(mut self, height: impl Into<Length>) -> Self {
|
|
|
|
|
self.height = height.into();
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn force_visible(mut self, idx: Option<usize>) -> Self {
|
|
|
|
|
self.scroll_to = idx;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl State {
|
2023-10-17 16:52:11 +02:00
|
|
|
fn next_tab_animation(&self) -> Option<&TabAnimationState> {
|
|
|
|
|
let now = Instant::now();
|
|
|
|
|
|
|
|
|
|
self.tab_animations
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|anim| now.duration_since(anim.start_time) <= TAB_ANIMATION_DURATION)
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 20:58:39 +02:00
|
|
|
pub fn offset(&self, bounds: Rectangle, content_bounds: Size) -> Vector {
|
2023-06-23 16:09:01 +02:00
|
|
|
if let Some(animation) = self.scroll_animation {
|
|
|
|
|
let percentage = {
|
2023-07-11 17:12:56 +02:00
|
|
|
let percentage = Instant::now()
|
2023-06-23 16:09:01 +02:00
|
|
|
.duration_since(animation.start_time)
|
|
|
|
|
.as_millis() as f32
|
2023-07-11 17:12:56 +02:00
|
|
|
/ SCROLL_ANIMATION_DURATION.as_millis() as f32;
|
2023-06-23 16:09:01 +02:00
|
|
|
|
2023-07-11 17:12:56 +02:00
|
|
|
ease(EaseInOutCubic, 0.0, 1.0, percentage)
|
2023-06-23 16:09:01 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Vector::new(
|
|
|
|
|
animation.start.absolute(bounds.width, content_bounds.width)
|
|
|
|
|
+ (animation.end.absolute(bounds.width, content_bounds.width)
|
|
|
|
|
- animation.start.absolute(bounds.width, content_bounds.width))
|
|
|
|
|
* percentage,
|
|
|
|
|
0.,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
Vector::new(
|
|
|
|
|
self.offset_x.absolute(bounds.width, content_bounds.width),
|
|
|
|
|
0.,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cleanup_old_animations(&mut self) {
|
2023-10-17 16:52:11 +02:00
|
|
|
let start_time = Instant::now();
|
|
|
|
|
|
2023-06-23 16:09:01 +02:00
|
|
|
if let Some(animation) = self.scroll_animation.as_ref() {
|
2023-10-17 16:52:11 +02:00
|
|
|
if start_time.duration_since(animation.start_time) > SCROLL_ANIMATION_DURATION {
|
2023-06-23 16:09:01 +02:00
|
|
|
self.scroll_animation.take();
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-26 21:05:31 +02:00
|
|
|
|
2023-10-17 16:52:11 +02:00
|
|
|
Self::discard_expired_tab_animations(&mut self.tab_animations, start_time);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Remove expired tab animations from the queue.
|
|
|
|
|
fn discard_expired_tab_animations(
|
|
|
|
|
tab_animations: &mut VecDeque<TabAnimationState>,
|
|
|
|
|
start_time: Instant,
|
|
|
|
|
) {
|
|
|
|
|
if let Some(mut animation) = tab_animations.pop_front() {
|
|
|
|
|
let mut set_next_start = false;
|
|
|
|
|
|
|
|
|
|
while start_time.duration_since(animation.start_time) > TAB_ANIMATION_DURATION {
|
|
|
|
|
set_next_start = true;
|
|
|
|
|
|
|
|
|
|
if let Some(next) = tab_animations.pop_front() {
|
|
|
|
|
animation = next;
|
|
|
|
|
continue;
|
2023-06-26 21:05:31 +02:00
|
|
|
}
|
2023-10-17 16:52:11 +02:00
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if set_next_start {
|
|
|
|
|
animation.start_time = start_time;
|
2023-06-26 21:05:31 +02:00
|
|
|
}
|
2023-10-17 16:52:11 +02:00
|
|
|
|
|
|
|
|
tab_animations.push_front(animation);
|
2023-06-26 21:05:31 +02:00
|
|
|
}
|
2023-06-22 20:58:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, Message, Renderer> Widget<Message, Renderer> for Tabs<'a, Message, Renderer>
|
|
|
|
|
where
|
|
|
|
|
Renderer: cosmic::iced_core::Renderer,
|
|
|
|
|
Renderer::Theme: ContainerStyleSheet<Style = theme::Container> + RuleStyleSheet,
|
|
|
|
|
Message: TabMessage,
|
|
|
|
|
{
|
|
|
|
|
fn width(&self) -> Length {
|
|
|
|
|
self.width
|
|
|
|
|
}
|
|
|
|
|
fn height(&self) -> Length {
|
|
|
|
|
self.height
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn id(&self) -> Option<Id> {
|
|
|
|
|
self.id.clone()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_id(&mut self, id: Id) {
|
|
|
|
|
self.id = Some(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn tag(&self) -> tree::Tag {
|
|
|
|
|
tree::Tag::of::<State>()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn state(&self) -> tree::State {
|
|
|
|
|
tree::State::Some(Box::new(State::default()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn children(&self) -> Vec<Tree> {
|
|
|
|
|
self.elements.iter().map(|elem| Tree::new(elem)).collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn diff(&mut self, tree: &mut Tree) {
|
|
|
|
|
tree.diff_children(&mut self.elements)
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-14 15:02:45 -08:00
|
|
|
fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &Limits) -> Node {
|
2023-06-22 20:58:39 +02:00
|
|
|
let limits = limits.width(self.width).height(self.height);
|
|
|
|
|
|
|
|
|
|
// calculate the smallest possible size
|
|
|
|
|
let child_limits = Limits::new(
|
|
|
|
|
Size::new(0.0, limits.min().height),
|
|
|
|
|
Size::new(f32::INFINITY, limits.max().height),
|
|
|
|
|
)
|
|
|
|
|
.width(Length::Shrink)
|
|
|
|
|
.height(Length::Shrink);
|
|
|
|
|
|
|
|
|
|
let mut nodes = self.elements[2..self.elements.len() - 2]
|
|
|
|
|
.iter()
|
2023-12-14 15:02:45 -08:00
|
|
|
.zip(tree.children.iter_mut())
|
|
|
|
|
.map(|(tab, tab_tree)| tab.as_widget().layout(tab_tree, renderer, &child_limits))
|
2023-06-22 20:58:39 +02:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
// sum up
|
|
|
|
|
let min_size = nodes
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|node| node.size())
|
|
|
|
|
.fold(Size::new(0., 0.), |a, b| Size {
|
|
|
|
|
width: a.width + b.width,
|
|
|
|
|
height: a.height.max(b.height),
|
|
|
|
|
});
|
|
|
|
|
let size = limits.resolve(min_size);
|
|
|
|
|
|
|
|
|
|
if min_size.width <= size.width {
|
|
|
|
|
// we don't need to scroll
|
|
|
|
|
|
|
|
|
|
// can we make every tab equal weight and keep the active large enough?
|
|
|
|
|
let children = if (size.width / (self.elements.len() as f32 - 5.)).ceil() as i32
|
|
|
|
|
>= MIN_ACTIVE_TAB_WIDTH
|
|
|
|
|
{
|
|
|
|
|
// just use a flex layout
|
|
|
|
|
cosmic::iced_core::layout::flex::resolve(
|
|
|
|
|
cosmic::iced_core::layout::flex::Axis::Horizontal,
|
|
|
|
|
renderer,
|
|
|
|
|
&limits,
|
|
|
|
|
0.into(),
|
|
|
|
|
0.,
|
|
|
|
|
cosmic::iced::Alignment::Center,
|
|
|
|
|
&self.elements[2..self.elements.len() - 2],
|
2023-12-14 15:02:45 -08:00
|
|
|
&mut tree.children[2..self.elements.len() - 2],
|
2023-06-22 20:58:39 +02:00
|
|
|
)
|
|
|
|
|
.children()
|
|
|
|
|
.to_vec()
|
|
|
|
|
} else {
|
|
|
|
|
// otherwise we need a more manual approach
|
|
|
|
|
let min_width = (size.width - MIN_ACTIVE_TAB_WIDTH as f32 - 4.)
|
|
|
|
|
/ (self.elements.len() as f32 - 6.);
|
|
|
|
|
let mut offset = 0.;
|
|
|
|
|
|
|
|
|
|
let mut nodes = self.elements[2..self.elements.len() - 3]
|
|
|
|
|
.iter()
|
2023-12-14 15:02:45 -08:00
|
|
|
.zip(tree.children[2..].iter_mut())
|
|
|
|
|
.map(|(tab, tab_tree)| {
|
2023-06-22 20:58:39 +02:00
|
|
|
let child_limits = Limits::new(
|
|
|
|
|
Size::new(min_width, limits.min().height),
|
|
|
|
|
Size::new(f32::INFINITY, limits.max().height),
|
|
|
|
|
)
|
|
|
|
|
.width(Length::Shrink)
|
|
|
|
|
.height(Length::Shrink);
|
|
|
|
|
|
2023-12-14 15:02:45 -08:00
|
|
|
let mut node = tab.as_widget().layout(tab_tree, renderer, &child_limits);
|
2023-06-22 20:58:39 +02:00
|
|
|
node.move_to(Point::new(offset, 0.));
|
|
|
|
|
offset += node.bounds().width;
|
|
|
|
|
node
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
nodes.push({
|
|
|
|
|
let mut node = Node::new(Size::new(4., limits.max().height));
|
|
|
|
|
node.move_to(Point::new(offset, 0.));
|
|
|
|
|
node
|
|
|
|
|
});
|
|
|
|
|
nodes
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// and add placeholder nodes for the not rendered scroll-buttons/rules
|
|
|
|
|
Node::with_children(
|
|
|
|
|
size,
|
|
|
|
|
vec![
|
|
|
|
|
Node::new(Size::new(0., 0.)),
|
|
|
|
|
Node::with_children(Size::new(0., 0.), vec![Node::new(Size::new(0., 0.))]),
|
|
|
|
|
]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.chain(children)
|
|
|
|
|
.chain(vec![
|
|
|
|
|
Node::with_children(Size::new(0., 0.), vec![Node::new(Size::new(0., 0.))]),
|
|
|
|
|
Node::new(Size::new(0., 0.)),
|
|
|
|
|
])
|
|
|
|
|
.collect::<Vec<_>>(),
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
// we scroll, so use the computed min size, but add scroll buttons.
|
|
|
|
|
let mut offset = 30.;
|
|
|
|
|
for node in &mut nodes {
|
|
|
|
|
node.move_to(Point::new(offset, 0.));
|
|
|
|
|
offset += node.bounds().width;
|
|
|
|
|
}
|
|
|
|
|
let last_position = Point::new(size.width - 34., 0.);
|
|
|
|
|
nodes.remove(nodes.len() - 1);
|
|
|
|
|
|
|
|
|
|
Node::with_children(
|
|
|
|
|
size,
|
|
|
|
|
vec![Node::new(Size::new(4., size.height)), {
|
|
|
|
|
let mut node = Node::with_children(
|
|
|
|
|
Size::new(16., 16.),
|
|
|
|
|
vec![Node::new(Size::new(16., 16.))],
|
|
|
|
|
);
|
|
|
|
|
node.move_to(Point::new(9., (size.height - 16.) / 2.));
|
|
|
|
|
node
|
|
|
|
|
}]
|
|
|
|
|
.into_iter()
|
|
|
|
|
.chain(nodes)
|
|
|
|
|
.chain(vec![
|
|
|
|
|
{
|
|
|
|
|
let mut node = Node::new(Size::new(4., size.height));
|
|
|
|
|
node.move_to(last_position);
|
|
|
|
|
node
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
let mut node = Node::with_children(
|
|
|
|
|
Size::new(16., 16.),
|
|
|
|
|
vec![Node::new(Size::new(16., 16.))],
|
|
|
|
|
);
|
|
|
|
|
node.move_to(last_position + Vector::new(9., (size.height - 16.) / 2.));
|
|
|
|
|
node
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
let mut node = Node::new(Size::new(4., size.height));
|
|
|
|
|
node.move_to(last_position + Vector::new(30., 0.));
|
|
|
|
|
node
|
|
|
|
|
},
|
|
|
|
|
])
|
|
|
|
|
.collect(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn draw(
|
|
|
|
|
&self,
|
|
|
|
|
tree: &Tree,
|
|
|
|
|
renderer: &mut Renderer,
|
|
|
|
|
theme: &<Renderer as cosmic::iced_core::Renderer>::Theme,
|
|
|
|
|
style: &renderer::Style,
|
|
|
|
|
layout: Layout<'_>,
|
2023-06-22 21:01:05 +02:00
|
|
|
cursor: mouse::Cursor,
|
2023-06-22 20:58:39 +02:00
|
|
|
viewport: &Rectangle,
|
|
|
|
|
) {
|
|
|
|
|
let state = tree.state.downcast_ref::<State>();
|
|
|
|
|
|
|
|
|
|
let mut bounds = layout.bounds();
|
|
|
|
|
let content_bounds = layout
|
|
|
|
|
.children()
|
|
|
|
|
.skip(2)
|
|
|
|
|
.take(self.elements.len() - 5)
|
|
|
|
|
.fold(Size::new(0., 0.), |a, b| Size {
|
|
|
|
|
width: a.width + b.bounds().width,
|
|
|
|
|
height: b.bounds().height,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let background_style = ContainerStyleSheet::appearance(
|
|
|
|
|
theme,
|
|
|
|
|
&theme::Container::custom(|theme| widget::container::Appearance {
|
2023-10-02 19:37:23 +02:00
|
|
|
icon_color: None,
|
2023-06-22 20:58:39 +02:00
|
|
|
text_color: None,
|
|
|
|
|
background: Some(Background::Color(Color::from(
|
|
|
|
|
theme.cosmic().palette.neutral_3,
|
|
|
|
|
))),
|
|
|
|
|
border_radius: 0.0.into(),
|
|
|
|
|
border_width: 0.0,
|
|
|
|
|
border_color: Color::TRANSPARENT,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
draw_background(renderer, &background_style, bounds);
|
|
|
|
|
|
|
|
|
|
let scrolling = content_bounds.width.floor() > bounds.width;
|
|
|
|
|
if scrolling {
|
|
|
|
|
bounds.width -= 64.;
|
|
|
|
|
bounds.x += 30.;
|
|
|
|
|
}
|
|
|
|
|
let offset = state.offset(bounds, content_bounds);
|
|
|
|
|
let offset_viewport = Rectangle {
|
|
|
|
|
x: bounds.x + offset.x,
|
|
|
|
|
y: bounds.y + offset.y,
|
|
|
|
|
..bounds
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if scrolling {
|
|
|
|
|
// we have scroll buttons
|
|
|
|
|
for ((scroll, state), layout) in self
|
|
|
|
|
.elements
|
|
|
|
|
.iter()
|
|
|
|
|
.take(2)
|
|
|
|
|
.zip(&tree.children)
|
|
|
|
|
.zip(layout.children())
|
|
|
|
|
{
|
2023-06-22 21:01:05 +02:00
|
|
|
scroll
|
|
|
|
|
.as_widget()
|
|
|
|
|
.draw(state, renderer, theme, style, layout, cursor, viewport);
|
2023-06-22 20:58:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderer.with_layer(bounds, |renderer| {
|
|
|
|
|
renderer.with_translation(Vector::new(-offset.x, -offset.y), |renderer| {
|
2023-10-17 16:52:11 +02:00
|
|
|
let tab_animation = state.next_tab_animation();
|
|
|
|
|
|
|
|
|
|
let percentage = if let Some(animation) = tab_animation {
|
2023-07-11 17:12:56 +02:00
|
|
|
let percentage = Instant::now()
|
2023-06-26 21:05:31 +02:00
|
|
|
.duration_since(animation.start_time)
|
|
|
|
|
.as_millis() as f32
|
2023-07-11 17:12:56 +02:00
|
|
|
/ TAB_ANIMATION_DURATION.as_millis() as f32;
|
|
|
|
|
ease(EaseOutCubic, 0.0, 1.0, percentage)
|
2023-06-26 21:05:31 +02:00
|
|
|
} else {
|
|
|
|
|
1.0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for ((tab, wstate), layout) in self.elements[2..self.elements.len() - 3]
|
2023-06-22 20:58:39 +02:00
|
|
|
.iter()
|
|
|
|
|
.zip(tree.children.iter().skip(2))
|
|
|
|
|
.zip(layout.children().skip(2))
|
|
|
|
|
{
|
2023-10-17 16:52:11 +02:00
|
|
|
let bounds = if let Some(animation) = tab_animation {
|
2023-06-26 21:05:31 +02:00
|
|
|
let id = tab.as_widget().id().unwrap();
|
|
|
|
|
let previous =
|
|
|
|
|
animation
|
|
|
|
|
.previous_bounds
|
|
|
|
|
.get(&id)
|
|
|
|
|
.copied()
|
|
|
|
|
.unwrap_or(Rectangle {
|
|
|
|
|
x: layout.position().x,
|
|
|
|
|
y: layout.position().y,
|
|
|
|
|
width: 0.,
|
|
|
|
|
height: layout.bounds().height,
|
|
|
|
|
});
|
|
|
|
|
let next = animation
|
|
|
|
|
.next_bounds
|
|
|
|
|
.get(&id)
|
|
|
|
|
.copied()
|
|
|
|
|
.unwrap_or(Rectangle {
|
|
|
|
|
x: layout.position().x,
|
|
|
|
|
y: layout.position().y,
|
|
|
|
|
width: 0.,
|
|
|
|
|
height: layout.bounds().height,
|
|
|
|
|
});
|
|
|
|
|
Rectangle {
|
|
|
|
|
x: previous.x + (next.x - previous.x) * percentage,
|
|
|
|
|
y: previous.y + (next.y - previous.y) * percentage,
|
|
|
|
|
width: previous.width + (next.width - previous.width) * percentage,
|
|
|
|
|
height: next.height,
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
layout.bounds()
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-22 21:01:05 +02:00
|
|
|
let cursor = match cursor {
|
|
|
|
|
mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset),
|
|
|
|
|
mouse::Cursor::Unavailable => mouse::Cursor::Unavailable,
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-26 21:05:31 +02:00
|
|
|
renderer.with_layer(bounds, |renderer| {
|
|
|
|
|
renderer.with_translation(
|
|
|
|
|
Vector {
|
|
|
|
|
x: bounds.x - layout.position().x,
|
|
|
|
|
y: bounds.y - layout.position().y,
|
|
|
|
|
},
|
|
|
|
|
|renderer| {
|
|
|
|
|
tab.as_widget().draw(
|
|
|
|
|
wstate,
|
|
|
|
|
renderer,
|
|
|
|
|
theme,
|
|
|
|
|
style,
|
|
|
|
|
layout,
|
|
|
|
|
cursor,
|
|
|
|
|
&offset_viewport,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
})
|
2023-06-22 20:58:39 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
self.elements[self.elements.len() - 3].as_widget().draw(
|
|
|
|
|
&tree.children[self.elements.len() - 3],
|
|
|
|
|
renderer,
|
|
|
|
|
theme,
|
|
|
|
|
style,
|
|
|
|
|
layout.children().nth(self.elements.len() - 3).unwrap(),
|
2023-06-22 21:01:05 +02:00
|
|
|
cursor,
|
2023-06-22 20:58:39 +02:00
|
|
|
viewport,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if !scrolling && self.group_focused {
|
|
|
|
|
// HACK, overdraw our rule at the edges
|
|
|
|
|
self.elements[0].as_widget().draw(
|
|
|
|
|
&tree.children[2].children[0],
|
|
|
|
|
renderer,
|
|
|
|
|
theme,
|
|
|
|
|
style,
|
|
|
|
|
layout.children().nth(2).unwrap().children().nth(0).unwrap(),
|
2023-06-22 21:01:05 +02:00
|
|
|
cursor,
|
2023-06-22 20:58:39 +02:00
|
|
|
viewport,
|
|
|
|
|
);
|
|
|
|
|
self.elements[self.elements.len() - 1].as_widget().draw(
|
|
|
|
|
&tree.children[self.elements.len() - 3],
|
|
|
|
|
renderer,
|
|
|
|
|
theme,
|
|
|
|
|
style,
|
|
|
|
|
layout.children().nth(self.elements.len() - 3).unwrap(),
|
2023-06-22 21:01:05 +02:00
|
|
|
cursor,
|
2023-06-22 20:58:39 +02:00
|
|
|
viewport,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if scrolling {
|
|
|
|
|
// we have scroll buttons
|
|
|
|
|
for ((scroll, state), layout) in self.elements
|
|
|
|
|
[self.elements.len() - 2..self.elements.len()]
|
|
|
|
|
.iter()
|
|
|
|
|
.zip(tree.children.iter().skip(self.elements.len() - 2))
|
|
|
|
|
.zip(layout.children().skip(self.elements.len() - 2))
|
|
|
|
|
{
|
2023-06-22 21:01:05 +02:00
|
|
|
scroll
|
|
|
|
|
.as_widget()
|
|
|
|
|
.draw(state, renderer, theme, style, layout, cursor, viewport);
|
2023-06-22 20:58:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn operate(
|
|
|
|
|
&self,
|
|
|
|
|
tree: &mut Tree,
|
|
|
|
|
layout: Layout<'_>,
|
|
|
|
|
renderer: &Renderer,
|
|
|
|
|
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
|
|
|
|
) {
|
|
|
|
|
let state = tree.state.downcast_mut::<State>();
|
2023-10-02 19:37:23 +02:00
|
|
|
let bounds = layout.bounds();
|
|
|
|
|
|
2023-06-23 16:09:01 +02:00
|
|
|
state.cleanup_old_animations();
|
2023-06-22 20:58:39 +02:00
|
|
|
|
2023-10-02 19:37:23 +02:00
|
|
|
operation.scrollable(
|
|
|
|
|
state,
|
|
|
|
|
self.id.as_ref(),
|
|
|
|
|
bounds,
|
|
|
|
|
Vector { x: 0.0, y: 0.0 }, /* seemingly unused */
|
|
|
|
|
);
|
2023-06-22 20:58:39 +02:00
|
|
|
|
2023-10-02 19:37:23 +02:00
|
|
|
operation.container(self.id.as_ref(), bounds, &mut |operation| {
|
2023-06-22 20:58:39 +02:00
|
|
|
self.elements[2..self.elements.len() - 3]
|
|
|
|
|
.iter()
|
|
|
|
|
.zip(tree.children.iter_mut().skip(2))
|
|
|
|
|
.zip(layout.children().skip(2))
|
|
|
|
|
.for_each(|((child, state), layout)| {
|
|
|
|
|
child
|
|
|
|
|
.as_widget()
|
|
|
|
|
.operate(state, layout, renderer, operation);
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn on_event(
|
|
|
|
|
&mut self,
|
|
|
|
|
tree: &mut Tree,
|
|
|
|
|
event: event::Event,
|
|
|
|
|
layout: Layout<'_>,
|
2023-06-22 21:01:05 +02:00
|
|
|
cursor: mouse::Cursor,
|
2023-06-22 20:58:39 +02:00
|
|
|
renderer: &Renderer,
|
|
|
|
|
clipboard: &mut dyn Clipboard,
|
|
|
|
|
shell: &mut Shell<'_, Message>,
|
2023-10-02 19:37:23 +02:00
|
|
|
viewport: &Rectangle,
|
2023-06-22 20:58:39 +02:00
|
|
|
) -> event::Status {
|
|
|
|
|
let state = tree.state.downcast_mut::<State>();
|
2023-06-23 16:09:01 +02:00
|
|
|
state.cleanup_old_animations();
|
2023-06-22 20:58:39 +02:00
|
|
|
|
2023-06-28 19:36:46 +02:00
|
|
|
let mut bounds = layout.bounds();
|
|
|
|
|
let content_bounds = layout.children().fold(Size::new(0., 0.), |a, b| Size {
|
|
|
|
|
width: a.width + b.bounds().width,
|
|
|
|
|
height: b.bounds().height,
|
|
|
|
|
});
|
|
|
|
|
let scrolling = content_bounds.width.floor() > bounds.width;
|
|
|
|
|
|
2023-06-26 21:05:31 +02:00
|
|
|
let current_state = self.elements[2..self.elements.len() - 3]
|
|
|
|
|
.iter()
|
|
|
|
|
.zip(layout.children().skip(2))
|
|
|
|
|
.map(|(element, layout)| (element.as_widget().id().unwrap(), layout.bounds()))
|
|
|
|
|
.collect::<HashMap<Id, Rectangle>>();
|
|
|
|
|
|
|
|
|
|
if state.last_state.is_none() {
|
|
|
|
|
state.last_state = Some(current_state.clone());
|
|
|
|
|
}
|
|
|
|
|
let last_state = state.last_state.as_mut().unwrap();
|
|
|
|
|
let unknown_keys = current_state
|
|
|
|
|
.keys()
|
|
|
|
|
.collect::<HashSet<_>>()
|
|
|
|
|
.symmetric_difference(&last_state.keys().collect::<HashSet<_>>())
|
|
|
|
|
.next()
|
|
|
|
|
.is_some();
|
2023-06-28 19:36:46 +02:00
|
|
|
|
|
|
|
|
enum Difference {
|
|
|
|
|
NewOrRemoved,
|
|
|
|
|
Movement,
|
|
|
|
|
Focus,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let changes = if unknown_keys {
|
|
|
|
|
Some(Difference::NewOrRemoved)
|
|
|
|
|
} else {
|
2023-07-31 17:36:32 +02:00
|
|
|
current_state
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|(a_id, a_bounds)| {
|
|
|
|
|
let Some(b_bounds) = last_state.get(a_id) else {
|
|
|
|
|
return Some(Difference::Movement);
|
|
|
|
|
};
|
|
|
|
|
(a_bounds != b_bounds).then(|| {
|
|
|
|
|
if a_bounds.position() != b_bounds.position() {
|
|
|
|
|
Difference::Movement
|
|
|
|
|
} else {
|
|
|
|
|
Difference::Focus
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.fold(None, |a, b| match (a, b) {
|
|
|
|
|
(None | Some(Difference::Movement), x) => Some(x),
|
|
|
|
|
(a, _) => a,
|
|
|
|
|
})
|
2023-06-28 19:36:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if unknown_keys || changes.is_some() {
|
|
|
|
|
if !scrolling || !matches!(changes, Some(Difference::Focus)) {
|
2023-10-17 16:52:11 +02:00
|
|
|
let start_time = Instant::now();
|
|
|
|
|
|
|
|
|
|
State::discard_expired_tab_animations(&mut state.tab_animations, start_time);
|
|
|
|
|
|
2023-06-28 19:36:46 +02:00
|
|
|
// new tab_animation
|
|
|
|
|
state.tab_animations.push_back(TabAnimationState {
|
|
|
|
|
previous_bounds: last_state.clone(),
|
|
|
|
|
next_bounds: current_state.clone(),
|
2023-10-17 16:52:11 +02:00
|
|
|
start_time,
|
2023-06-28 19:36:46 +02:00
|
|
|
});
|
|
|
|
|
}
|
2023-06-26 21:05:31 +02:00
|
|
|
|
|
|
|
|
// update last_state
|
|
|
|
|
*last_state = current_state;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-22 20:58:39 +02:00
|
|
|
if scrolling {
|
|
|
|
|
bounds.x += 30.;
|
|
|
|
|
bounds.width -= 64.;
|
|
|
|
|
}
|
|
|
|
|
let offset = state.offset(bounds, content_bounds);
|
|
|
|
|
|
|
|
|
|
if let Some(idx) = self.scroll_to {
|
|
|
|
|
state.scroll_to = Some(idx);
|
|
|
|
|
}
|
|
|
|
|
if let Some(idx) = state.scroll_to.take() {
|
|
|
|
|
if scrolling {
|
|
|
|
|
let tab_bounds = layout.children().nth(idx + 2).unwrap().bounds();
|
|
|
|
|
let left_offset = tab_bounds.x - layout.bounds().x - 30.;
|
|
|
|
|
let right_offset = left_offset + tab_bounds.width + 4.;
|
|
|
|
|
let scroll_width = bounds.width;
|
|
|
|
|
let current_start = 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()
|
|
|
|
|
{
|
2023-06-23 16:09:01 +02:00
|
|
|
let new_offset = if (left_offset - current_start).abs()
|
2023-06-22 20:58:39 +02:00
|
|
|
< (right_offset - current_end).abs()
|
|
|
|
|
{
|
|
|
|
|
AbsoluteOffset {
|
|
|
|
|
x: left_offset,
|
|
|
|
|
y: 0.,
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
AbsoluteOffset {
|
|
|
|
|
x: right_offset - scroll_width,
|
|
|
|
|
y: 0.,
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-23 16:09:01 +02:00
|
|
|
state.scroll_animation = Some(ScrollAnimationState {
|
|
|
|
|
start_time: Instant::now(),
|
|
|
|
|
start: Offset::Absolute(offset.x),
|
|
|
|
|
end: Offset::Absolute(new_offset.x),
|
|
|
|
|
});
|
|
|
|
|
state.offset_x = Offset::Absolute(new_offset.x);
|
2023-06-22 20:58:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
shell.publish(Message::scrolled());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut messages = Vec::new();
|
|
|
|
|
let mut internal_shell = Shell::new(&mut messages);
|
|
|
|
|
|
|
|
|
|
let len = self.elements.len();
|
2023-06-22 21:01:05 +02:00
|
|
|
let result = if scrolling
|
|
|
|
|
&& cursor
|
|
|
|
|
.position()
|
|
|
|
|
.map(|pos| pos.x < bounds.x)
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
{
|
2023-06-22 20:58:39 +02:00
|
|
|
self.elements[0..2]
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.zip(&mut tree.children)
|
|
|
|
|
.zip(layout.children())
|
|
|
|
|
.map(|((child, state), layout)| {
|
|
|
|
|
child.as_widget_mut().on_event(
|
|
|
|
|
state,
|
|
|
|
|
event.clone(),
|
|
|
|
|
layout,
|
2023-06-22 21:01:05 +02:00
|
|
|
cursor,
|
2023-06-22 20:58:39 +02:00
|
|
|
renderer,
|
|
|
|
|
clipboard,
|
|
|
|
|
&mut internal_shell,
|
2023-10-02 19:37:23 +02:00
|
|
|
viewport,
|
2023-06-22 20:58:39 +02:00
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.fold(event::Status::Ignored, event::Status::merge)
|
2023-06-22 21:01:05 +02:00
|
|
|
} else if scrolling
|
|
|
|
|
&& cursor
|
|
|
|
|
.position()
|
|
|
|
|
.map(|pos| pos.x >= bounds.x + bounds.width)
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
{
|
2023-06-22 20:58:39 +02:00
|
|
|
self.elements[len - 3..len]
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.zip(tree.children.iter_mut().skip(len - 3))
|
|
|
|
|
.zip(layout.children().skip(len - 3))
|
|
|
|
|
.map(|((child, state), layout)| {
|
|
|
|
|
child.as_widget_mut().on_event(
|
|
|
|
|
state,
|
|
|
|
|
event.clone(),
|
|
|
|
|
layout,
|
2023-06-22 21:01:05 +02:00
|
|
|
cursor,
|
2023-06-22 20:58:39 +02:00
|
|
|
renderer,
|
|
|
|
|
clipboard,
|
|
|
|
|
&mut internal_shell,
|
2023-10-02 19:37:23 +02:00
|
|
|
viewport,
|
2023-06-22 20:58:39 +02:00
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.fold(event::Status::Ignored, event::Status::merge)
|
|
|
|
|
} else {
|
|
|
|
|
self.elements[2..len - 3]
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.zip(tree.children.iter_mut().skip(2))
|
|
|
|
|
.zip(layout.children().skip(2))
|
|
|
|
|
.map(|((child, state), layout)| {
|
2023-06-22 21:01:05 +02:00
|
|
|
let cursor = match cursor {
|
|
|
|
|
mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset),
|
|
|
|
|
mouse::Cursor::Unavailable => mouse::Cursor::Unavailable,
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-22 20:58:39 +02:00
|
|
|
child.as_widget_mut().on_event(
|
|
|
|
|
state,
|
|
|
|
|
event.clone(),
|
|
|
|
|
layout,
|
2023-06-22 21:01:05 +02:00
|
|
|
cursor,
|
2023-06-22 20:58:39 +02:00
|
|
|
renderer,
|
|
|
|
|
clipboard,
|
|
|
|
|
&mut internal_shell,
|
2023-10-02 19:37:23 +02:00
|
|
|
viewport,
|
2023-06-22 20:58:39 +02:00
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.fold(event::Status::Ignored, event::Status::merge)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::mem::drop(internal_shell);
|
|
|
|
|
for mut message in messages {
|
|
|
|
|
if let Some(offset) = message.populate_scroll(AbsoluteOffset {
|
|
|
|
|
x: state.offset_x.absolute(bounds.width, content_bounds.width),
|
|
|
|
|
y: 0.,
|
|
|
|
|
}) {
|
|
|
|
|
state.scroll_to(offset);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shell.publish(message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn mouse_interaction(
|
|
|
|
|
&self,
|
|
|
|
|
tree: &Tree,
|
|
|
|
|
layout: Layout<'_>,
|
2023-06-22 21:01:05 +02:00
|
|
|
cursor: mouse::Cursor,
|
2023-06-22 20:58:39 +02:00
|
|
|
viewport: &Rectangle,
|
|
|
|
|
renderer: &Renderer,
|
|
|
|
|
) -> mouse::Interaction {
|
|
|
|
|
let state = tree.state.downcast_ref::<State>();
|
|
|
|
|
|
|
|
|
|
let mut bounds = layout.bounds();
|
|
|
|
|
let content_bounds = layout.children().fold(Size::new(0., 0.), |a, b| Size {
|
|
|
|
|
width: a.width + b.bounds().width,
|
|
|
|
|
height: b.bounds().height,
|
|
|
|
|
});
|
|
|
|
|
let scrolling = content_bounds.width.floor() > bounds.width;
|
|
|
|
|
|
|
|
|
|
if scrolling {
|
|
|
|
|
bounds.width -= 64.;
|
|
|
|
|
bounds.x += 30.;
|
|
|
|
|
}
|
|
|
|
|
let offset = state.offset(bounds, content_bounds);
|
|
|
|
|
let offset_viewport = &Rectangle {
|
|
|
|
|
y: bounds.y + offset.y,
|
|
|
|
|
x: bounds.x + offset.x,
|
|
|
|
|
..bounds
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-22 21:01:05 +02:00
|
|
|
if scrolling
|
|
|
|
|
&& cursor
|
|
|
|
|
.position()
|
|
|
|
|
.map(|pos| pos.x < bounds.x)
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
{
|
2023-06-22 20:58:39 +02:00
|
|
|
self.elements[0..2]
|
|
|
|
|
.iter()
|
|
|
|
|
.zip(&tree.children)
|
|
|
|
|
.zip(layout.children())
|
|
|
|
|
.map(|((child, state), layout)| {
|
2023-06-22 21:01:05 +02:00
|
|
|
child
|
|
|
|
|
.as_widget()
|
|
|
|
|
.mouse_interaction(state, layout, cursor, viewport, renderer)
|
2023-06-22 20:58:39 +02:00
|
|
|
})
|
|
|
|
|
.max()
|
2023-06-22 21:01:05 +02:00
|
|
|
} else if scrolling
|
|
|
|
|
&& cursor
|
|
|
|
|
.position()
|
|
|
|
|
.map(|pos| pos.x >= bounds.x + bounds.width)
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
{
|
2023-06-22 20:58:39 +02:00
|
|
|
self.elements[self.elements.len() - 3..self.elements.len()]
|
|
|
|
|
.iter()
|
|
|
|
|
.zip(tree.children.iter().skip(self.elements.len() - 3))
|
|
|
|
|
.zip(layout.children().skip(self.elements.len() - 3))
|
|
|
|
|
.map(|((child, state), layout)| {
|
2023-06-22 21:01:05 +02:00
|
|
|
child
|
|
|
|
|
.as_widget()
|
|
|
|
|
.mouse_interaction(state, layout, cursor, viewport, renderer)
|
2023-06-22 20:58:39 +02:00
|
|
|
})
|
|
|
|
|
.max()
|
|
|
|
|
} else {
|
|
|
|
|
self.elements[2..self.elements.len() - 3]
|
|
|
|
|
.iter()
|
|
|
|
|
.zip(tree.children.iter().skip(2))
|
|
|
|
|
.zip(layout.children().skip(2))
|
|
|
|
|
.map(|((child, state), layout)| {
|
2023-06-22 21:01:05 +02:00
|
|
|
let cursor = match cursor {
|
|
|
|
|
mouse::Cursor::Available(point) => mouse::Cursor::Available(point + offset),
|
|
|
|
|
mouse::Cursor::Unavailable => mouse::Cursor::Unavailable,
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-22 20:58:39 +02:00
|
|
|
child.as_widget().mouse_interaction(
|
|
|
|
|
state,
|
|
|
|
|
layout,
|
2023-06-22 21:01:05 +02:00
|
|
|
cursor,
|
2023-06-22 20:58:39 +02:00
|
|
|
offset_viewport,
|
|
|
|
|
renderer,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.max()
|
|
|
|
|
}
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn overlay<'b>(
|
|
|
|
|
&'b mut self,
|
|
|
|
|
tree: &'b mut Tree,
|
|
|
|
|
layout: Layout<'_>,
|
|
|
|
|
renderer: &Renderer,
|
|
|
|
|
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
|
|
|
|
overlay::from_children(&mut self.elements, tree, layout, renderer)
|
|
|
|
|
}
|
|
|
|
|
}
|