- assets/logo.svg (dream sky koala) + assets/icon.png hasil render 512x512 (skrip render: oksvg/rasterx, sparkle di-inline karena <use> tidak didukung) - tema: langit malam ungu (#140E24), kartu violet, primer dusk pink, aksen amber<->magenta yang berdenyut; varian terang jadi lavender lembut - ikon monokrom dari Iconify ikut diwarnai dusk pink (#c25c9e), bukan indigo lama - screenshot README diperbarui
142 lines
4.5 KiB
Go
142 lines
4.5 KiB
Go
package ui
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/canvas"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/dialog"
|
|
"fyne.io/fyne/v2/layout"
|
|
"fyne.io/fyne/v2/storage"
|
|
"fyne.io/fyne/v2/theme"
|
|
"fyne.io/fyne/v2/widget"
|
|
|
|
"lazyrun/assets"
|
|
)
|
|
|
|
// catatan: widget.Label di Fyne 2.8 selalu memesan tinggi ~2x ukuran font,
|
|
// jadi teks satu baris di UI utama memakai canvas.Text supaya rapat.
|
|
|
|
// textLine membuat teks satu baris berukuran & berwarna tertentu.
|
|
func textLine(text string, size float32, bold bool, col fyne.ThemeColorName) *canvas.Text {
|
|
t := canvas.NewText(text, theme.Color(col))
|
|
t.TextSize = size
|
|
t.TextStyle = fyne.TextStyle{Bold: bold}
|
|
return t
|
|
}
|
|
|
|
// heading adalah nama aplikasi di header.
|
|
func heading(text string) *canvas.Text {
|
|
return textLine(text, 22, true, theme.ColorNameForeground)
|
|
}
|
|
|
|
// subheading adalah nama shortcut di kartu.
|
|
func subheading(text string) *canvas.Text {
|
|
return textLine(text, 15, true, theme.ColorNameForeground)
|
|
}
|
|
|
|
// caption adalah teks sekunder kecil (tagline, path, versi).
|
|
func caption(text string) *canvas.Text {
|
|
return textLine(text, 12, false, colorNameMuted)
|
|
}
|
|
|
|
// sectionLabel adalah judul bagian huruf kapital.
|
|
func sectionLabel(text string) *canvas.Text {
|
|
return textLine(strings.ToUpper(text), 11, true, colorNameMuted)
|
|
}
|
|
|
|
// muted adalah teks sekunder yang bisa wrap/truncate (dipakai di dialog).
|
|
func muted(text string) *widget.Label {
|
|
label := widget.NewLabel(text)
|
|
label.Importance = widget.LowImportance
|
|
return label
|
|
}
|
|
|
|
// card membungkus konten dengan latar membulat.
|
|
func card(content fyne.CanvasObject) fyne.CanvasObject {
|
|
bg := canvas.NewRectangle(theme.Color(colorNameCard))
|
|
bg.CornerRadius = 12
|
|
return container.NewStack(bg, container.NewPadded(content))
|
|
}
|
|
|
|
// chip adalah label kecil dengan latar berwarna.
|
|
func chip(text string, bg fyne.ThemeColorName) fyne.CanvasObject {
|
|
object, _ := chipLabel(text, bg)
|
|
return object
|
|
}
|
|
|
|
// chipLabel mengembalikan chip sekaligus teksnya supaya bisa diperbarui.
|
|
func chipLabel(text string, bg fyne.ThemeColorName) (fyne.CanvasObject, *canvas.Text) {
|
|
rect := canvas.NewRectangle(theme.Color(bg))
|
|
rect.CornerRadius = 7
|
|
label := textLine(text, 11, true, theme.ColorNameForeground)
|
|
label.Alignment = fyne.TextAlignCenter
|
|
return container.NewStack(rect, container.NewPadded(label)), label
|
|
}
|
|
|
|
// accentBar adalah garis gradien brand di bawah header.
|
|
func accentBar() *canvas.LinearGradient {
|
|
bar := canvas.NewLinearGradient(brandAmber, brandMagenta, 90)
|
|
bar.SetMinSize(fyne.NewSize(0, 3))
|
|
return bar
|
|
}
|
|
|
|
// iconButton adalah tombol ikon tanpa label, gaya datar.
|
|
func iconButton(icon fyne.Resource, tapped func()) *widget.Button {
|
|
button := widget.NewButtonWithIcon("", icon, tapped)
|
|
button.Importance = widget.LowImportance
|
|
return button
|
|
}
|
|
|
|
// thumb menampilkan ikon shortcut, atau fallback kalau file ikonnya hilang.
|
|
func thumb(path string, fallback fyne.Resource) fyne.CanvasObject {
|
|
var image *canvas.Image
|
|
if path != "" {
|
|
if _, err := os.Stat(path); err == nil {
|
|
image = canvas.NewImageFromURI(storage.NewFileURI(path))
|
|
}
|
|
}
|
|
if image == nil {
|
|
image = canvas.NewImageFromResource(theme.NewThemedResource(fallback))
|
|
}
|
|
image.FillMode = canvas.ImageFillContain
|
|
image.CornerRadius = 8
|
|
image.SetMinSize(fyne.NewSize(36, 36))
|
|
return container.NewGridWrap(fyne.NewSize(36, 36), image)
|
|
}
|
|
|
|
// newLogo mengembalikan ikon LazyRun (dipakai untuk keperluan animasi).
|
|
func newLogo() *canvas.Image {
|
|
image := canvas.NewImageFromResource(fyne.NewStaticResource("lazyrun.png", assets.Icon))
|
|
image.FillMode = canvas.ImageFillContain
|
|
return image
|
|
}
|
|
|
|
// logoBox menampilkan ikon LazyRun pada ukuran tetap.
|
|
func logoBox(image *canvas.Image, size float32) fyne.CanvasObject {
|
|
return container.NewGridWrap(fyne.NewSize(size, size), image)
|
|
}
|
|
|
|
// closeRow menempatkan tombol tutup di kanan atas isi dialog, supaya
|
|
// selalu ada jalan keluar yang terlihat walau tombol bawah terpotong.
|
|
func closeRow(hide func()) fyne.CanvasObject {
|
|
return container.NewHBox(spacer(), iconButton(theme.CancelIcon(), hide))
|
|
}
|
|
|
|
// showDialog menampilkan dialog dengan ukuran yang disesuaikan ke window.
|
|
// Isi dialog sebaiknya dibungkus VScroll supaya MinSize-nya kecil (Fyne tidak
|
|
// bisa mengecilkan dialog di bawah MinSize konten).
|
|
func showDialog(d *dialog.CustomDialog, win fyne.Window, width, height float32) {
|
|
size := win.Canvas().Size()
|
|
d.Resize(fyne.NewSize(
|
|
fyne.Min(width, size.Width*0.9),
|
|
fyne.Min(height, size.Height*0.85),
|
|
))
|
|
d.Show()
|
|
}
|
|
|
|
// spacer adalah pengisi ruang kosong.
|
|
func spacer() fyne.CanvasObject { return layout.NewSpacer() }
|