UI: tema brand LazyRun, layout kartu, dan dialog yang lebih rapi
- Tema custom (indigo/cyan dari ikon, sudut membulat) mengikuti varian sistem - Header compact: logo + wordmark, tombol aksi di kanan - Kartu per shortcut: ikon, nama, meta singkat, chip lokasi, tombol ikon - Teks satu baris pakai canvas.Text (widget.Label Fyne 2.8 memesan tinggi ~2x) - Dialog: judul bagian, ikon folder pada Browse, tombol Install primary
This commit is contained in:
1 parent
da31f7d7d7
commit
1935b5d83d
7 files changed
+316
-65
No files matched your search
@@ -8,6 +8,7 @@ import (
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"lazyrun/internal/shortcut"
|
||||
@@ -27,9 +28,10 @@ var (
|
||||
func (ui *app) showExecutableDialog() {
|
||||
var binPath, iconPath string
|
||||
|
||||
binLabel := widget.NewLabel(T("none"))
|
||||
iconLabel := widget.NewLabel(T("icon_auto"))
|
||||
binLabel := muted(T("none"))
|
||||
iconLabel := muted(T("icon_auto"))
|
||||
nameEntry := widget.NewEntry()
|
||||
nameEntry.SetPlaceHolder("Winbox")
|
||||
category := widget.NewSelect(categories, nil)
|
||||
category.SetSelected("Utility")
|
||||
menu := widget.NewCheck(T("check_menu"), nil)
|
||||
@@ -37,7 +39,7 @@ func (ui *app) showExecutableDialog() {
|
||||
desktop := widget.NewCheck(T("check_desktop"), nil)
|
||||
desktop.SetChecked(true)
|
||||
|
||||
browseBin := widget.NewButton(T("browse"), func() {
|
||||
browseBin := widget.NewButtonWithIcon(T("browse"), theme.FolderOpenIcon(), func() {
|
||||
pickFile(ui.win, nil, func(path string) {
|
||||
binPath = path
|
||||
binLabel.SetText(filepath.Base(path))
|
||||
@@ -46,7 +48,7 @@ func (ui *app) showExecutableDialog() {
|
||||
}
|
||||
})
|
||||
})
|
||||
browseIcon := widget.NewButton(T("browse"), func() {
|
||||
browseIcon := widget.NewButtonWithIcon(T("browse"), theme.FolderOpenIcon(), func() {
|
||||
pickFile(ui.win, imageFilter, func(path string) {
|
||||
iconPath = path
|
||||
iconLabel.SetText(filepath.Base(path))
|
||||
@@ -54,7 +56,7 @@ func (ui *app) showExecutableDialog() {
|
||||
})
|
||||
|
||||
var d *dialog.CustomDialog
|
||||
install := widget.NewButton(T("install"), func() {
|
||||
install := widget.NewButtonWithIcon(T("install"), theme.DownloadIcon(), func() {
|
||||
switch {
|
||||
case binPath == "":
|
||||
dialog.ShowInformation(T("err_title"), T("need_binary"), ui.win)
|
||||
@@ -90,14 +92,17 @@ func (ui *app) showExecutableDialog() {
|
||||
content := container.NewVBox(
|
||||
form,
|
||||
widget.NewSeparator(),
|
||||
heading(T("section_location")),
|
||||
sectionTitle(T("section_location")),
|
||||
menu,
|
||||
desktop,
|
||||
container.NewHBox(spacer(), widget.NewButton(T("cancel"), func() { d.Hide() }), install),
|
||||
muted(T("exec_hint")),
|
||||
container.NewHBox(spacer(),
|
||||
widget.NewButton(T("cancel"), func() { d.Hide() }),
|
||||
install),
|
||||
)
|
||||
|
||||
d = dialog.NewCustom(T("new_exec_title"), "", content, ui.win)
|
||||
d.Resize(fyne.NewSize(520, 460))
|
||||
d.Resize(fyne.NewSize(560, 470))
|
||||
d.Show()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"lazyrun/internal/browser"
|
||||
@@ -27,7 +28,8 @@ func (ui *app) showWebAppDialog() {
|
||||
urlEntry := widget.NewEntry()
|
||||
urlEntry.SetPlaceHolder("https://web.whatsapp.com")
|
||||
nameEntry := widget.NewEntry()
|
||||
iconLabel := widget.NewLabel(T("icon_auto"))
|
||||
nameEntry.SetPlaceHolder("WhatsApp Web")
|
||||
iconLabel := muted(T("icon_auto"))
|
||||
picker := widget.NewSelect(browserNames(ui.browsers), nil)
|
||||
picker.SetSelected(ui.browsers[0].Name)
|
||||
menu := widget.NewCheck(T("check_menu"), nil)
|
||||
@@ -51,7 +53,7 @@ func (ui *app) showWebAppDialog() {
|
||||
}
|
||||
}
|
||||
|
||||
browseIcon := widget.NewButton(T("browse"), func() {
|
||||
browseIcon := widget.NewButtonWithIcon(T("browse"), theme.FolderOpenIcon(), func() {
|
||||
pickFile(ui.win, imageFilter, func(path string) {
|
||||
iconPath = path
|
||||
iconLabel.SetText(filepath.Base(path))
|
||||
@@ -59,7 +61,7 @@ func (ui *app) showWebAppDialog() {
|
||||
})
|
||||
|
||||
var d *dialog.CustomDialog
|
||||
install := widget.NewButton(T("install"), func() {
|
||||
install := widget.NewButtonWithIcon(T("install"), theme.DownloadIcon(), func() {
|
||||
switch {
|
||||
case strings.TrimSpace(urlEntry.Text) == "":
|
||||
dialog.ShowInformation(T("err_title"), T("need_url"), ui.win)
|
||||
@@ -96,14 +98,17 @@ func (ui *app) showWebAppDialog() {
|
||||
content := container.NewVBox(
|
||||
form,
|
||||
widget.NewSeparator(),
|
||||
heading(T("section_location")),
|
||||
sectionTitle(T("section_location")),
|
||||
menu,
|
||||
desktop,
|
||||
container.NewHBox(spacer(), widget.NewButton(T("cancel"), func() { d.Hide() }), install),
|
||||
muted(T("web_hint")),
|
||||
container.NewHBox(spacer(),
|
||||
widget.NewButton(T("cancel"), func() { d.Hide() }),
|
||||
install),
|
||||
)
|
||||
|
||||
d = dialog.NewCustom(T("new_web_title"), "", content, ui.win)
|
||||
d.Resize(fyne.NewSize(520, 440))
|
||||
d.Resize(fyne.NewSize(560, 470))
|
||||
d.Show()
|
||||
}
|
||||
|
||||
|
||||
+90
-42
@@ -5,12 +5,11 @@ import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"lazyrun/assets"
|
||||
@@ -40,26 +39,50 @@ func Run(a fyne.App, version string) {
|
||||
|
||||
// build menyiapkan window utama tanpa menampilkannya (dipakai Run dan tes headless).
|
||||
func build(a fyne.App, version string) (*app, error) {
|
||||
a.Settings().SetTheme(brand{theme.DefaultTheme()})
|
||||
|
||||
ui := &app{version: version}
|
||||
a.SetIcon(fyne.NewStaticResource("lazyrun.png", assets.Icon))
|
||||
|
||||
ui.browsers = browser.Detect()
|
||||
ui.rows = container.NewVBox()
|
||||
ui.win = a.NewWindow(T("app_title"))
|
||||
ui.win.Resize(fyne.NewSize(760, 540))
|
||||
|
||||
top := container.NewHBox(
|
||||
widget.NewButton(T("btn_executable"), ui.showExecutableDialog),
|
||||
widget.NewButton(T("btn_webapp"), ui.showWebAppDialog),
|
||||
)
|
||||
bottom := container.NewBorder(nil, nil,
|
||||
widget.NewButton(T("check_updates"), ui.checkUpdates),
|
||||
widget.NewLabel("v"+version))
|
||||
body := container.NewBorder(heading(T("installed")), nil, nil, nil, container.NewVScroll(ui.rows))
|
||||
ui.win.SetContent(container.NewBorder(top, bottom, nil, nil, body))
|
||||
ui.win.Resize(fyne.NewSize(840, 600))
|
||||
|
||||
st, err := store.Load(xdg.MetadataPath())
|
||||
ui.store = st
|
||||
|
||||
addExec := widget.NewButtonWithIcon(T("btn_executable"), theme.ContentAddIcon(), ui.showExecutableDialog)
|
||||
addExec.Importance = widget.HighImportance
|
||||
addWeb := widget.NewButtonWithIcon(T("btn_webapp"), theme.ContentAddIcon(), ui.showWebAppDialog)
|
||||
|
||||
leftHeader := container.NewHBox(logoImage(40), container.NewVBox(
|
||||
heading("LazyRun"),
|
||||
caption(T("tagline")),
|
||||
))
|
||||
rightHeader := container.NewHBox(addExec, addWeb)
|
||||
header := container.NewBorder(nil, nil, leftHeader, rightHeader)
|
||||
|
||||
update := widget.NewButtonWithIcon(T("check_updates"), theme.DownloadIcon(), ui.checkUpdates)
|
||||
footer := container.NewBorder(
|
||||
widget.NewSeparator(), nil,
|
||||
update,
|
||||
caption("v"+version),
|
||||
)
|
||||
|
||||
top := container.NewVBox(header, widget.NewSeparator(), sectionLabel(T("installed")))
|
||||
body := container.NewBorder(
|
||||
top,
|
||||
nil, nil, nil,
|
||||
container.NewVScroll(ui.rows))
|
||||
|
||||
content := container.NewBorder(
|
||||
nil,
|
||||
container.NewPadded(footer),
|
||||
nil, nil,
|
||||
container.NewPadded(body),
|
||||
)
|
||||
ui.win.SetContent(content)
|
||||
ui.reload()
|
||||
return ui, err
|
||||
}
|
||||
@@ -70,7 +93,7 @@ func (ui *app) reload() {
|
||||
}
|
||||
ui.rows.RemoveAll()
|
||||
if len(ui.store.Items) == 0 {
|
||||
ui.rows.Add(widget.NewLabel(T("empty")))
|
||||
ui.rows.Add(emptyState())
|
||||
}
|
||||
for _, sc := range ui.store.Items {
|
||||
ui.rows.Add(ui.row(sc))
|
||||
@@ -78,40 +101,66 @@ func (ui *app) reload() {
|
||||
}
|
||||
|
||||
func (ui *app) row(sc store.Shortcut) fyne.CanvasObject {
|
||||
title := widget.NewLabelWithStyle(sc.Name, fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||
info := widget.NewLabel(describe(sc))
|
||||
info := container.NewVBox(
|
||||
subheading(sc.Name),
|
||||
caption(metaText(sc)),
|
||||
)
|
||||
|
||||
open := widget.NewButton(T("folder"), func() {
|
||||
chips := make([]fyne.CanvasObject, 0, 3)
|
||||
if sc.Targets.Menu {
|
||||
chips = append(chips, chip(T("chip_menu"), colorNameChipMenu))
|
||||
}
|
||||
if sc.Targets.Desktop {
|
||||
chips = append(chips, chip(T("chip_desktop"), colorNameChipDesk))
|
||||
}
|
||||
if sc.Broken() {
|
||||
chips = append(chips, chip(T("chip_broken"), colorNameChipBroken))
|
||||
}
|
||||
|
||||
actions := container.NewHBox(chips...)
|
||||
actions.Add(iconButton(theme.FolderOpenIcon(), func() {
|
||||
if err := exec.Command("xdg-open", filepath.Dir(sc.DesktopPath)).Start(); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
}
|
||||
})
|
||||
remove := widget.NewButton(T("delete"), func() { ui.confirmDelete(sc) })
|
||||
}))
|
||||
actions.Add(iconButton(theme.DeleteIcon(), func() { ui.confirmDelete(sc) }))
|
||||
|
||||
return container.NewBorder(nil, widget.NewSeparator(), nil,
|
||||
container.NewHBox(open, remove),
|
||||
container.NewVBox(title, info))
|
||||
return container.NewVBox(
|
||||
card(container.NewBorder(nil, nil,
|
||||
container.NewHBox(thumb(sc.IconPath, fallbackIcon(sc)), info),
|
||||
actions)),
|
||||
)
|
||||
}
|
||||
|
||||
func describe(sc store.Shortcut) string {
|
||||
targets := make([]string, 0, 2)
|
||||
if sc.Targets.Menu {
|
||||
targets = append(targets, T("check_menu"))
|
||||
}
|
||||
if sc.Targets.Desktop {
|
||||
targets = append(targets, T("check_desktop"))
|
||||
}
|
||||
loc := strings.Join(targets, " + ")
|
||||
|
||||
kind := "Executable"
|
||||
// metaText menampilkan tipe shortcut dan penanda singkatnya.
|
||||
func metaText(sc store.Shortcut) string {
|
||||
if sc.Type == store.TypeWebApp {
|
||||
kind = "Web"
|
||||
if host := hostOf(sc.URL); host != "" {
|
||||
return "Web · " + host
|
||||
}
|
||||
line := kind + " · " + loc
|
||||
if sc.Broken() {
|
||||
line += " · " + T("broken")
|
||||
return "Web"
|
||||
}
|
||||
return line
|
||||
if sc.BinaryPath != "" {
|
||||
return "Executable · " + filepath.Base(sc.BinaryPath)
|
||||
}
|
||||
return "Executable"
|
||||
}
|
||||
|
||||
func fallbackIcon(sc store.Shortcut) fyne.Resource {
|
||||
if sc.Type == store.TypeWebApp {
|
||||
return theme.ComputerIcon()
|
||||
}
|
||||
return theme.FileApplicationIcon()
|
||||
}
|
||||
|
||||
func emptyState() fyne.CanvasObject {
|
||||
hint := muted(T("empty_hint"))
|
||||
hint.Alignment = fyne.TextAlignCenter
|
||||
return container.NewCenter(container.NewVBox(
|
||||
container.NewCenter(logoImage(88)),
|
||||
subheading(T("empty_title")),
|
||||
hint,
|
||||
))
|
||||
}
|
||||
|
||||
func (ui *app) confirmDelete(sc store.Shortcut) {
|
||||
@@ -173,8 +222,7 @@ func (ui *app) added(sc store.Shortcut, err error, closeDialog func()) {
|
||||
dialog.ShowInformation(T("success_title"), fmt.Sprintf(T("success_msg"), sc.Name), ui.win)
|
||||
}
|
||||
|
||||
func heading(text string) *widget.Label {
|
||||
return widget.NewLabelWithStyle(text, fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||
// sectionTitle adalah judul bagian di dalam dialog.
|
||||
func sectionTitle(text string) fyne.CanvasObject {
|
||||
return container.NewVBox(sectionLabel(text), widget.NewSeparator())
|
||||
}
|
||||
|
||||
func spacer() fyne.CanvasObject { return layout.NewSpacer() }
|
||||
@@ -26,10 +26,13 @@ func TestBuildMainWindow(t *testing.T) {
|
||||
ui.checkUpdates()
|
||||
}
|
||||
|
||||
func TestDescribe(t *testing.T) {
|
||||
sc := store.Shortcut{Type: store.TypeWebApp, Targets: store.Targets{Menu: true}, DesktopPath: "/nonexistent/x.desktop"}
|
||||
got := describe(sc)
|
||||
if got != "Web · Application menu · missing" && got != "Web · Menu aplikasi · hilang" {
|
||||
t.Errorf("describe() = %q", got)
|
||||
func TestMetaText(t *testing.T) {
|
||||
sc := store.Shortcut{Type: store.TypeWebApp, URL: "https://example.com/x", Targets: store.Targets{Menu: true}}
|
||||
if got := metaText(sc); got != "Web · example.com" {
|
||||
t.Errorf("metaText() = %q", got)
|
||||
}
|
||||
exe := store.Shortcut{Type: store.TypeExecutable, BinaryPath: "/home/u/.local/bin/winbox"}
|
||||
if got := metaText(exe); got != "Executable · winbox" {
|
||||
t.Errorf("metaText() = %q", got)
|
||||
}
|
||||
}
|
||||
+10
-4
@@ -8,10 +8,15 @@ import (
|
||||
// tr menyimpan teks UI: index 0 = Inggris, 1 = Indonesia.
|
||||
var tr = map[string][2]string{
|
||||
"app_title": {"LazyRun — lazy shortcut build", "LazyRun — lazy shortcut build"},
|
||||
"btn_executable": {"+ Executable", "+ Executable"},
|
||||
"btn_webapp": {"+ Web App", "+ Aplikasi Web"},
|
||||
"tagline": {"lazy shortcut build", "lazy shortcut build"},
|
||||
"btn_executable": {"Executable", "Executable"},
|
||||
"btn_webapp": {"Web App", "Aplikasi Web"},
|
||||
"installed": {"Installed shortcuts", "Shortcut terinstall"},
|
||||
"empty": {"No shortcuts yet. Add one with the buttons above.", "Belum ada shortcut. Tambahkan lewat tombol di atas."},
|
||||
"empty_title": {"Nothing here yet", "Belum ada apa-apa"},
|
||||
"empty_hint": {"Create your first shortcut with the buttons in the corner.", "Buat shortcut pertamamu lewat tombol di pojok kanan atas."},
|
||||
"chip_menu": {"Menu", "Menu"},
|
||||
"chip_desktop": {"Desktop", "Desktop"},
|
||||
"chip_broken": {"file missing", "file hilang"},
|
||||
"none": {"not selected", "belum dipilih"},
|
||||
"icon_auto": {"automatic", "otomatis"},
|
||||
"browse": {"Browse...", "Pilih..."},
|
||||
@@ -33,6 +38,8 @@ var tr = map[string][2]string{
|
||||
"section_location": {"Shortcut location", "Lokasi shortcut"},
|
||||
"check_menu": {"Application menu", "Menu aplikasi"},
|
||||
"check_desktop": {"Desktop", "Desktop"},
|
||||
"exec_hint": {"The binary is copied to ~/.local/bin and can be uninstalled from the list.", "Binary disalin ke ~/.local/bin dan bisa dihapus lagi dari daftar."},
|
||||
"web_hint": {"The icon is fetched from the site's favicon if you leave it empty.", "Ikon diambil otomatis dari favicon situs kalau dibiarkan kosong."},
|
||||
|
||||
"confirm_delete_title": {"Uninstall shortcut", "Hapus shortcut"},
|
||||
"confirm_delete_msg": {"Remove %s?", "Hapus %s?"},
|
||||
@@ -47,7 +54,6 @@ var tr = map[string][2]string{
|
||||
"need_url": {"Enter a URL first.", "Masukkan URL dulu."},
|
||||
"need_target": {"Pick at least one location: menu or desktop.", "Pilih minimal satu lokasi: menu atau desktop."},
|
||||
"no_browser": {"No supported browser found. Install Chrome, Chromium, Brave, Edge, Vivaldi, or Firefox first.", "Browser yang didukung tidak ditemukan. Install Chrome, Chromium, Brave, Edge, Vivaldi, atau Firefox dulu."},
|
||||
"broken": {"missing", "hilang"},
|
||||
|
||||
"check_updates": {"Check for Updates", "Cek Update"},
|
||||
"not_appimage": {"This build is not an AppImage (version %s). Update from where you got it.", "Build ini bukan AppImage (versi %s). Update dari sumber kamu mendapatkannya."},
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"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 {
|
||||
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))
|
||||
}
|
||||
|
||||
// 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.SetMinSize(fyne.NewSize(36, 36))
|
||||
return container.NewGridWrap(fyne.NewSize(36, 36), image)
|
||||
}
|
||||
|
||||
// logoImage menampilkan ikon LazyRun pada ukuran tertentu.
|
||||
func logoImage(size float32) fyne.CanvasObject {
|
||||
image := canvas.NewImageFromResource(fyne.NewStaticResource("lazyrun.png", assets.Icon))
|
||||
image.FillMode = canvas.ImageFillContain
|
||||
image.SetMinSize(fyne.NewSize(size, size))
|
||||
return container.NewGridWrap(fyne.NewSize(size, size), image)
|
||||
}
|
||||
|
||||
// spacer adalah pengisi ruang kosong.
|
||||
func spacer() fyne.CanvasObject { return layout.NewSpacer() }
|
||||
@@ -0,0 +1,80 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
// Palet brand LazyRun, diambil dari ikon aplikasi.
|
||||
var (
|
||||
brandIndigo = color.NRGBA{R: 0x4F, G: 0x46, B: 0xE5, A: 0xFF}
|
||||
brandCyan = color.NRGBA{R: 0x06, G: 0xB6, B: 0xD4, A: 0xFF}
|
||||
)
|
||||
|
||||
// Nama warna tambahan di luar tema standar Fyne.
|
||||
const (
|
||||
colorNameCard fyne.ThemeColorName = "lazyrunCard"
|
||||
colorNameMuted fyne.ThemeColorName = "lazyrunMuted"
|
||||
colorNameChipMenu fyne.ThemeColorName = "lazyrunChipMenu"
|
||||
colorNameChipDesk fyne.ThemeColorName = "lazyrunChipDesktop"
|
||||
colorNameChipBroken fyne.ThemeColorName = "lazyrunChipBroken"
|
||||
)
|
||||
|
||||
// brand menimpa sebagian warna/ukuran tema bawaan; terang/gelap tetap ikut sistem.
|
||||
type brand struct{ fyne.Theme }
|
||||
|
||||
func (b brand) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
|
||||
dark := variant == theme.VariantDark
|
||||
switch name {
|
||||
case theme.ColorNamePrimary:
|
||||
return pick(dark, color.NRGBA{R: 0x7C, G: 0x7A, B: 0xFF, A: 0xFF}, brandIndigo)
|
||||
case theme.ColorNameBackground:
|
||||
return pick(dark, color.NRGBA{R: 0x13, G: 0x13, B: 0x1B, A: 0xFF}, color.NRGBA{R: 0xF5, G: 0xF5, B: 0xFA, A: 0xFF})
|
||||
case colorNameCard:
|
||||
return pick(dark, color.NRGBA{R: 0x1D, G: 0x1D, B: 0x28, A: 0xFF}, color.NRGBA{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF})
|
||||
case theme.ColorNameInputBackground:
|
||||
return pick(dark, color.NRGBA{R: 0x1A, G: 0x1A, B: 0x24, A: 0xFF}, color.NRGBA{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF})
|
||||
case theme.ColorNameButton, theme.ColorNameMenuBackground:
|
||||
return pick(dark, color.NRGBA{R: 0x22, G: 0x22, B: 0x2E, A: 0xFF}, color.NRGBA{R: 0xEC, G: 0xEC, B: 0xF4, A: 0xFF})
|
||||
case theme.ColorNameOverlayBackground:
|
||||
return pick(dark, color.NRGBA{R: 0x1B, G: 0x1B, B: 0x25, A: 0xFF}, color.NRGBA{R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF})
|
||||
case theme.ColorNameInputBorder, theme.ColorNameSeparator:
|
||||
return pick(dark, color.NRGBA{R: 0x32, G: 0x32, B: 0x44, A: 0xFF}, color.NRGBA{R: 0xDC, G: 0xDC, B: 0xE8, A: 0xFF})
|
||||
case colorNameMuted:
|
||||
return pick(dark, color.NRGBA{R: 0x9C, G: 0x9C, B: 0xB4, A: 0xFF}, color.NRGBA{R: 0x6B, G: 0x6B, B: 0x80, A: 0xFF})
|
||||
case colorNameChipMenu:
|
||||
return withAlpha(brandIndigo, pick[uint8](dark, 0x44, 0x26))
|
||||
case colorNameChipDesk:
|
||||
return withAlpha(brandCyan, pick[uint8](dark, 0x3D, 0x2C))
|
||||
case colorNameChipBroken:
|
||||
return withAlpha(color.NRGBA{R: 0xE0, G: 0x4F, B: 0x4F, A: 0xFF}, pick[uint8](dark, 0x4A, 0x2E))
|
||||
}
|
||||
return b.Theme.Color(name, variant)
|
||||
}
|
||||
|
||||
// Size membuat sudut tombol/kartu lebih membulat dari tema bawaan.
|
||||
func (b brand) Size(name fyne.ThemeSizeName) float32 {
|
||||
switch name {
|
||||
case theme.SizeNameButtonRadius, theme.SizeNameInputRadius:
|
||||
return 8
|
||||
case theme.SizeNameCardRadius, theme.SizeNamePopupRadius:
|
||||
return 14
|
||||
case theme.SizeNamePadding:
|
||||
return 5
|
||||
}
|
||||
return b.Theme.Size(name)
|
||||
}
|
||||
|
||||
func pick[T any](cond bool, yes, no T) T {
|
||||
if cond {
|
||||
return yes
|
||||
}
|
||||
return no
|
||||
}
|
||||
|
||||
func withAlpha(c color.NRGBA, a uint8) color.Color {
|
||||
c.A = a
|
||||
return c
|
||||
}
|
||||
Reference in new issue
Block a user