let rgb = fn (r: int, g: int, b: int) {
{ r: r, g: g, b: b, a: 255 }
};
let ink = rgb(26, 21, 19);
let panel = rgb(38, 32, 29);
let raised = rgb(52, 45, 41);
let chalk = rgb(243, 234, 224);
let dim = rgb(140, 128, 118);
let accent = rgb(240, 96, 63);
let springy = { stiffness: 220, damping: 20 };
let design = { name: "Design", color: rgb(224, 185, 106) };
let code = { name: "Code", color: rgb(92, 170, 150) };
let docs = { name: "Docs", color: rgb(84, 120, 210) };
let ideas = [
{ title: "Sketch stage", tag: design },
{ title: "Spring checks", tag: code },
{ title: "Write guide", tag: docs },
{ title: "Fix scanner", tag: code },
{ title: "Pick palette", tag: design },
{ title: "Ship 0.1", tag: docs },
];
let task = fn (id: int, done: bool) {
let idea = ideas[id % ideas.length()];
{ id: id, title: idea.title, tag: idea.tag, done: done }
};
let check = fn (on: bool) {
Flex {
style: {
width: 20, height: 20, corner_radius: 10,
main_alignment: "center", cross_alignment: "center",
border: { width: 2, color: match on { true => accent, false => dim } },
background_color: match on { true => accent, false => panel },
transition: { background_color: springy, border: springy },
},
children: [
Flex {
style: {
width: 8, height: 8, corner_radius: 4,
background_color: ink,
transform: { scale: match on { true => 1, false => 0 } },
transition: { transform: springy },
},
children: [],
},
],
}
};
let row = fn (task: map, toggle: function, remove: function) {
let on = task.done;
Flex {
key: "task-" + task.id,
initial: { opacity: 0, transform: { translate_x: -16 } },
exit: { opacity: 0, transform: { translate_x: 16 } },
style: {
width: "grow", padding: 4, gap: 6, corner_radius: 8,
cross_alignment: "center",
background_color: panel,
transition: { opacity: "spring", transform: springy, background_color: "spring" },
},
hover_style: { background_color: raised },
children: [
Flex {
mouse_down: toggle,
style: { width: "grow", padding: 6, gap: 10, cross_alignment: "center", cursor: "pointer" },
children: [
check(on),
Text {
text: task.title,
style: {
width: "grow", size: 14,
color: match on { true => dim, false => chalk },
transition: { color: "spring" },
},
},
Flex {
style: { padding: { top: 2, right: 6, bottom: 2, left: 6 }, corner_radius: 6, background_color: task.tag.color },
children: [ Text { text: task.tag.name, style: { size: 11, weight: "bold", color: ink } } ],
},
],
},
Flex {
mouse_down: remove,
style: {
width: 26, height: 26, corner_radius: 6,
main_alignment: "center", cross_alignment: "center", cursor: "pointer",
transition: { background_color: springy },
},
hover_style: { background_color: accent },
children: [ Text { text: "×", style: { size: 18, color: dim }, hover_style: { color: ink }, hover_with_parent: true } ],
},
],
}
};
let tab = fn (label: String, current: String, pick: function) {
let active = label == current;
Flex {
mouse_down: fn () { pick(label); },
style: {
width: "grow", padding: 6, corner_radius: 6,
main_alignment: "center", cursor: "pointer",
background_color: match active { true => raised, false => panel },
transition: { background_color: springy },
},
children: [
Text { text: label, style: { size: 13, color: match active { true => chalk, false => dim } } },
],
}
};
let main = fn () {
state items = [task(0, false), task(1, true), task(2, false), task(3, false)];
state next = 4;
state filter = "All";
let total = items.length();
let finished = 0;
for (i in 0..total) {
if items[i].done { finished = finished + 1; }
}
let progress = match total { 0 => 0, _ => finished / total };
let visible = fn (t: map) {
match filter { "Open" => !t.done, "Done" => t.done, _ => true }
};
let listed = 0;
for (i in 0..total) {
if visible(items[i]) { listed = listed + 1; }
}
let add = fn () {
let grown = items.length() + 1;
let fresh = task(next, false);
items = for (j in 0..grown) {
match j < grown - 1 { true => items[j], false => fresh }
};
next = next + 1;
};
let toggle = fn (i: int) {
let flipped = task(items[i].id, !items[i].done);
items = for (j in 0..items.length()) {
match j == i { true => flipped, false => items[j] }
};
};
let remove = fn (i: int) {
let shrunk = items.length() - 1;
items = for (j in 0..shrunk) {
match j < i { true => items[j], false => items[j + 1] }
};
};
Flex {
style: {
width: "grow", height: "grow", padding: 16, gap: 12,
direction: "column",
background_color: ink,
},
children: [
Flex {
style: { width: "grow", cross_alignment: "end" },
children: [
Text { text: "Today", style: { width: "grow", size: 22, weight: "bold", color: chalk } },
Text { text: finished + " of " + total + " done", style: { size: 13, color: dim } },
],
},
Flex {
style: { width: "grow", height: 4, corner_radius: 2, background_color: panel },
children: [
Flex {
style: {
width: "grow", height: 4, corner_radius: 2,
background_color: accent,
transform_origin: "left",
transform: { scale_x: progress },
transition: { transform: springy },
},
children: [],
},
],
},
Flex {
style: { width: "grow", padding: 3, gap: 3, corner_radius: 8, background_color: panel },
children: [
tab("All", filter, fn (f: String) { filter = f; }),
tab("Open", filter, fn (f: String) { filter = f; }),
tab("Done", filter, fn (f: String) { filter = f; }),
],
},
Flex {
style: {
width: "grow", height: "grow", gap: 6,
direction: "column",
overflow: "scroll", scroll_follow_end: true,
stagger: { step: 0.04 },
},
children: [
...for (i in 0..total) {
match visible(items[i]) {
true => row(items[i], fn () { toggle(i); }, fn () { remove(i); }),
false => {},
}
},
match listed {
0 => Text {
text: match filter {
"Open" => "Nothing open. Well done.",
"Done" => "Nothing finished yet.",
_ => "Nothing to do. Add a task below.",
},
style: { width: "grow", align: "center", size: 13, color: dim },
},
_ => {},
},
],
},
Flex {
mouse_down: add,
style: {
width: "grow", padding: 10, corner_radius: 8,
main_alignment: "center", cursor: "pointer",
border: { width: 1, color: raised, style: "dashed" },
},
hover_style: { background_color: panel },
children: [ Text { text: "+ Add task", style: { size: 13, color: dim } } ],
},
],
}
};