196 lines
5.2 KiB
Plaintext
196 lines
5.2 KiB
Plaintext
import { AboutSlint, Button, Palette, HorizontalBox, ScrollView } from "std-widgets.slint";
|
|
|
|
enum PreviewKind {
|
|
None,
|
|
Thumbhash,
|
|
Thumbnail,
|
|
}
|
|
|
|
struct ImagePreview {
|
|
asset_id: string,
|
|
image: image,
|
|
kind: PreviewKind,
|
|
}
|
|
|
|
struct ImageBucket {
|
|
key: string,
|
|
title: string,
|
|
count: int,
|
|
previews: [ImagePreview],
|
|
}
|
|
|
|
enum ViewState {
|
|
Hidden,
|
|
NearView,
|
|
InView,
|
|
}
|
|
|
|
export global Global {
|
|
in-out property <length> min-image-size: 100px;
|
|
in-out property <length> image-margin: 2px;
|
|
in-out property <[ImageBucket]> image-buckets: [
|
|
{ key: "2026-02-01", title: "Feb 1, 2026", count: 12 },
|
|
{ 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);
|
|
}
|
|
|
|
|
|
component Header inherits Rectangle {
|
|
width: 100%;
|
|
height: 48px;
|
|
background: Palette.alternate-background;
|
|
|
|
HorizontalBox {
|
|
height: parent.height;
|
|
Text {
|
|
text: "immich";
|
|
}
|
|
}
|
|
}
|
|
|
|
component ImagePreview inherits Rectangle {
|
|
in property <image> preview;
|
|
in property <length> size: 32px;
|
|
width: size;
|
|
height: size;
|
|
|
|
Image {
|
|
width: 100%;
|
|
height: 100%;
|
|
source: preview;
|
|
}
|
|
|
|
touch := TouchArea {
|
|
clicked => {
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
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-y: Math.ceil(bucket.count / count-x);
|
|
|
|
function calc-image-size() -> length {
|
|
let remaining-length = Math.mod(self.width, min-size-with-margin);
|
|
min-image-size + remaining-length / count-x
|
|
}
|
|
|
|
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;
|
|
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);
|
|
// }
|
|
|
|
// TODO: don't render subtree when self.view-state = Hidden
|
|
title-box := HorizontalBox {
|
|
alignment: space-between;
|
|
height: 36px;
|
|
|
|
Text {
|
|
text: bucket.title;
|
|
}
|
|
|
|
// TODO: checkbox thingy
|
|
Text {
|
|
text: "O";
|
|
}
|
|
}
|
|
|
|
// TODO: don't render subtree when self.view-state = Hidden
|
|
image-box := Rectangle {
|
|
width: 100%;
|
|
height: count-y * image-size-with-margin;
|
|
|
|
for preview[i] in bucket.previews : ImagePreview {
|
|
preview: preview.image;
|
|
size: image-size;
|
|
x: Global.image-margin / 2 + Math.mod(i, count-x) * (Global.image-margin + image-size);
|
|
y: Math.floor(i / count-x) * (image-size + Global.image-margin);
|
|
}
|
|
}
|
|
}
|
|
|
|
// component ImageViewer inherits Rectangle {
|
|
// in property <image> image;
|
|
|
|
// width: 100%;
|
|
// height: 100%;
|
|
|
|
// background: black;
|
|
// }
|
|
|
|
export component AppWindow inherits Window {
|
|
VerticalLayout {
|
|
padding: 0px;
|
|
width: 100%;
|
|
|
|
Header {}
|
|
|
|
ScrollView {
|
|
width: 100%;
|
|
mouse-drag-pan-enabled: true;
|
|
|
|
// 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;
|
|
// }
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|