Initial commit
This commit is contained in:
195
ui/app-window.slint
Normal file
195
ui/app-window.slint
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user