Optimize timeline visibility

Due to "Recursion detected" bugs in slint, we need to calculate the
layout of the timeline in Rust. This is ugly, but works.
This commit is contained in:
2026-04-26 12:02:38 +02:00
parent ab01696537
commit 10fcd546ce
3 changed files with 219 additions and 100 deletions

View File

@@ -12,17 +12,20 @@ struct ImagePreview {
kind: PreviewKind,
}
enum Visibility {
Hidden,
NearView,
InView,
}
struct ImageBucket {
key: string,
title: string,
count: int,
previews: [ImagePreview],
}
enum ViewState {
Hidden,
NearView,
InView,
y: length,
height: length,
visibility: Visibility,
}
export global Global {
@@ -33,8 +36,11 @@ export global Global {
{ key: "2026-02-02", title: "Feb 2, 2026", count: 12 },
{ key: "2026-02-03", title: "Feb 3, 2026", count: 12 },
];
callback bucket-view-state(string, ViewState);
in property <length> timeline-height;
in property <length> timeline-width;
in property <length> timeline-scroll;
callback set-timeline-width(length);
callback timeline-scrolled(length);
}
@@ -71,18 +77,11 @@ component ImagePreview inherits Rectangle {
component TimelineBlock inherits VerticalLayout {
in property <int> index: -1;
in property <ImageBucket> bucket;
function calc-view-state() -> ViewState {
// TODO: fix this function
return ViewState.InView;
}
in property <ViewState> view-state: calc-view-state();
in-out property <ImageBucket> bucket;
property <length> min-image-size: Global.min-image-size;
property <length> min-size-with-margin: min-image-size + Global.image-margin;
property <int> count-x: self.width / min-size-with-margin;
property <int> count-x: Math.floor(self.width / min-size-with-margin); // TODO: or is it ceil?
property <int> count-y: Math.ceil(bucket.count / count-x);
function calc-image-size() -> length {
@@ -93,19 +92,16 @@ component TimelineBlock inherits VerticalLayout {
property <length> image-size: calc-image-size();
property <length> image-size-with-margin: image-size + Global.image-margin;
min-width: min-image-size;
alignment: start;
property <length> title-box-height: 36px;
height: title-box.height + count-y * image-size-with-margin;
// FIXME: this triggers recursion errors in slint.
// changed view-state => {
// Global.bucket-view-state(bucket.key, view-state);
// }
y: bucket.y;
min-width: min-image-size;
alignment: start;
// TODO: don't render subtree when self.view-state = Hidden
title-box := HorizontalBox {
alignment: space-between;
height: 36px;
height: title-box-height;
Text {
text: bucket.title;
@@ -117,7 +113,6 @@ component TimelineBlock inherits VerticalLayout {
}
}
// TODO: don't render subtree when self.view-state = Hidden
image-box := Rectangle {
width: 100%;
height: count-y * image-size-with-margin;
@@ -141,6 +136,15 @@ component TimelineBlock inherits VerticalLayout {
// }
export component AppWindow inherits Window {
out property <length> window-height: self.height;
// Do not base preferred-width on children
preferred-width: 480px;
changed width => {
Global.set-timeline-width(self.width);
}
VerticalLayout {
padding: 0px;
width: 100%;
@@ -148,45 +152,26 @@ export component AppWindow inherits Window {
Header {}
ScrollView {
width: 100%;
mouse-drag-pan-enabled: true;
viewport-height: rect.height;
// viewport-height: 20000px;
// self.viewport-y goes into the negative. invert it to make things easier.
// property <length> viewport-top-y: -self.viewport-y;
// property <length> viewport-bottom-y: viewport-top-y + self.height;
// property <length> almost-visible-margin: self.height;
// function is-visible(top-y: length, bottom-y: length) -> bool {
// top-y <= viewport-bottom-y && viewport-top-y <= bottom-y
// }
// function is-almost-visible(top-y: length, bottom-y: length) -> bool {
// top-y <= (viewport-bottom-y + almost-visible-margin)
// && (viewport-top-y - almost-visible-margin) <= bottom-y
// }
// function view-state(top-y: length, bottom-y: length) -> ViewState {
// if is-visible(top-y, bottom-y) {
// return ViewState.InView;
// }
// if is-almost-visible(top-y, bottom-y) {
// return ViewState.NearView;
// }
// return ViewState.Hidden;
// }
changed viewport-y => {
Global.timeline-scrolled(-self.viewport-y);
}
VerticalLayout {
alignment: start;
padding: 0px;
width: 100%;
for bucket[i] in Global.image-buckets : TimelineBlock {
width: 100%;
index: i;
bucket: bucket;
// view-state: view-state(self.y, self.y + self.height);
rect := Rectangle {
y: 0;
x: 0;
width: root.width;
height: Global.timeline-height;
preferred-width: self.width;
preferred-height: self.height;
for bucket[i] in Global.image-buckets : Rectangle {
if bucket.visibility == Visibility.InView : TimelineBlock {
width: root.width;
index: i;
bucket: bucket;
}
}
}
}