Initial commit

This commit is contained in:
2026-04-06 11:19:26 +02:00
commit 68e8205276
9 changed files with 7764 additions and 0 deletions

195
ui/app-window.slint Normal file
View File

@@ -0,0 +1,195 @@
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);
}
}
}
}
}