Compare commits

..

No commits in common. "main" and "v0.4.0" have entirely different histories.
main ... v0.4.0

28 changed files with 501 additions and 2009 deletions

View File

@ -2,12 +2,5 @@
[![Go Reference](https://pkg.go.dev/badge/git.tebibyte.media/tomo/nasin.svg)](https://pkg.go.dev/git.tebibyte.media/tomo/nasin)
Nasin provides an easy way to write applications with Tomo. To get started, take
a look at the [examples](examples) directory and the
[online documentation](https://pkg.go.dev/git.tebibyte.media/tomo/nasin).
Related repositories:
- [Tomo API](https://git.tebibyte.media/tomo/tomo): The API that all other parts
of the toolkit agree on
- [Objects](https://git.tebibyte.media/tomo/objects): A standard collection of
re-usable objects and other GUI components
Nasin provides an easy way to write applications with Tomo. Parts of Tomo that
aren't the GUI toolkit may be found here.

View File

@ -1,15 +1,9 @@
package nasin
import "fmt"
import "log"
import "flag"
import "image"
import "strings"
import "net/url"
import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/objects"
import "git.tebibyte.media/tomo/tomo/canvas"
import "git.tebibyte.media/tomo/nasin/internal/registrar"
import "git.tebibyte.media/tomo/nasin/internal/registry"
// Application represents an application object.
type Application interface {
@ -20,35 +14,6 @@ type Application interface {
Init () error
}
// ApplicationURLOpener is an application that can open a URL.
type ApplicationURLOpener interface {
Application
// OpenURL opens a new window with the contents of the given URL. If the
// given URL is unsupported, it returns an error (for example, an image
// viewer is not expected to open a text file).
//
// Applications should support the file:// scheme at the very least, and
// should also support others like http:// and https:// if possible.
OpenURL (*url.URL) error
// OpenNone is called when the application is launched without any URLs
// to open. The application may create some sort of default starting
// window, or call tomo.Stop().
OpenNone ()
}
// ApplicationFlagAdder is an application that supports reading command line
// flags.
type ApplicationFlagAdder interface {
Application
// AddFlags is called before Init and given the default flag set that
// Nasin uses to parse command line arguments. Note that when this
// method is called, Tomo will not yet be initialized.
AddFlags (*flag.FlagSet)
}
// ApplicationDescription describes the name and type of an application.
type ApplicationDescription struct {
// The name of the application.
@ -111,111 +76,24 @@ type ApplicationRole string; const (
RoleChecklist ApplicationRole = "Checklist"
)
// Icon returns the icon ID for this role.
func (role ApplicationRole) Icon () tomo.Icon {
if role == "" {
return tomo.IconApplication
} else {
return tomo.Icon("Application" + strings.ReplaceAll(string(role), " ", ""))
}
}
// RunApplication is like tomo.Run, but runs an application. If something fails
// to initialize, an error is written to the standard logger.
func RunApplication (application Application) {
// TODO: see #4
if application, ok := application.(ApplicationFlagAdder); ok {
application.AddFlags(flag.CommandLine)
}
flag.Parse()
err := registrar.RegisterBackend()
if err != nil { log.Fatalln("nasin: could not register backend:", err) }
err := registry.Init()
if err != nil { log.Fatal("nasin: could not init registry:", err) }
err = tomo.Run(func () {
err := registrar.SetTheme()
if err != nil { log.Fatalln("nasin: could not set theme:", err) }
err = application.Init()
if err != nil { log.Fatalln("nasin: could not run application:", err) }
// open URLs
args := flag.Args()
applicationOpenUrls(application, args...)
err := application.Init()
if err != nil { log.Fatal("nasin: could not run application:", err) }
})
if err != nil { log.Fatalln("nasin: could not run application:", err) }
if err != nil { log.Fatal("nasin: could not run application:", err) }
}
// NewApplicationWindow creates a window for an application. It will
// automatically set window information to signal to the OS that the window is
// owned by the application. The window's icon will be automatically set by
// looking for an icon with the name of the application's ID. If that is not
// found, the default icon for the application's ApplicationRole will used.
func NewApplicationWindow (application Application, bounds image.Rectangle) (tomo.Window, error) {
// owned by the application.
func NewApplicationWindow (application Application, bounds image.Rectangle) (tomo.MainWindow, error) {
window, err := tomo.NewWindow(bounds)
if err != nil { return nil, err }
description := application.Describe()
window.SetTitle(description.Name)
setApplicationWindowIcon(window, description)
window.SetTitle(application.Describe().String())
return window, nil
}
func applicationOpenUrls (application Application, args ...string) {
if application, ok := application.(ApplicationURLOpener); ok {
if len(args) <= 0 {
application.OpenNone()
}
openedAny := false
for _, arg := range flag.Args() {
ur, err := url.Parse(arg)
if err != nil {
log.Fatalf (
"nasin: invalid URL %v: %v",
arg, err)
}
if ur.Scheme == "" {
ur.Scheme = "file"
}
err = application.OpenURL(ur)
if err != nil {
dialog, err := objects.NewDialogOk (
objects.DialogError, nil,
"Could Not Open URL",
fmt.Sprintf (
"Could not open %v: %v",
arg, err),
func () {
if !openedAny {
application.OpenNone()
}
})
if err != nil { log.Fatal(err) }
dialog.SetVisible(true)
}
}
} else {
log.Fatal("nasin: this application cannot open URLs")
}
}
func setApplicationWindowIcon (window tomo.Window, description ApplicationDescription) {
allSizes := func (icon tomo.Icon) (sizes []canvas.Texture) {
small := icon.Texture(tomo.IconSizeSmall)
medium := icon.Texture(tomo.IconSizeMedium)
large := icon.Texture(tomo.IconSizeLarge)
if small != nil { sizes = append(sizes, small) }
if medium != nil { sizes = append(sizes, medium) }
if large != nil { sizes = append(sizes, large) }
return sizes
}
if sizes := allSizes(tomo.Icon(description.ID)); len(sizes) > 0 {
window.SetIcon(sizes...)
return
}
if sizes := allSizes(description.Role.Icon()); len(sizes) > 0 {
window.SetIcon(sizes...)
return
}
}

13
go.mod
View File

@ -3,18 +3,17 @@ module git.tebibyte.media/tomo/nasin
go 1.20
require (
git.tebibyte.media/tomo/backend v0.4.1
git.tebibyte.media/tomo/objects v0.19.0
git.tebibyte.media/tomo/tomo v0.38.0
git.tebibyte.media/tomo/tomo v0.31.0
git.tebibyte.media/tomo/x v0.7.0
git.tebibyte.media/tomo/xdg v0.1.0
golang.org/x/image v0.11.0
)
require (
git.tebibyte.media/tomo/typeset v0.7.1 // indirect
git.tebibyte.media/tomo/typeset v0.7.0 // indirect
git.tebibyte.media/tomo/xgbkb v1.0.1 // indirect
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect
github.com/jezek/xgb v1.1.1 // indirect
github.com/jezek/xgbutil v0.0.0-20231116234834-47f30c120111 // indirect
github.com/jezek/xgb v1.1.0 // indirect
github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 // indirect
golang.org/x/image v0.11.0 // indirect
)

20
go.sum
View File

@ -1,12 +1,10 @@
git.tebibyte.media/sashakoshka/xgbkb v1.0.0/go.mod h1:pNcE6TRO93vHd6q42SdwLSTTj25L0Yzggz7yLe0JV6Q=
git.tebibyte.media/tomo/backend v0.4.1 h1:rpkyTAfp/PE56aSqKNxtSN+D/aBSW8wjJ/vTtSgqQhE=
git.tebibyte.media/tomo/backend v0.4.1/go.mod h1:2G04zmKbzf5fA/xzsjoQRrN55BWZkB+vknFxd3ialZ0=
git.tebibyte.media/tomo/objects v0.19.0 h1:ibnAoPvkPTiuJGlqQDnoGVZBQiUT/GZo1qqvDDUCK4w=
git.tebibyte.media/tomo/objects v0.19.0/go.mod h1:GP5BZ6lc2F2D0OxERfr9r46PIk7jFlpq2/BWOpoQDqc=
git.tebibyte.media/tomo/tomo v0.38.0 h1:K5TP67RxnszudeNfmGZiU5cFTRjFueXiI3NCsgw+05U=
git.tebibyte.media/tomo/tomo v0.38.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
git.tebibyte.media/tomo/typeset v0.7.1 h1:aZrsHwCG5ZB4f5CruRFsxLv5ezJUCFUFsQJJso2sXQ8=
git.tebibyte.media/tomo/typeset v0.7.1/go.mod h1:PwDpSdBF3l/EzoIsa2ME7QffVVajnTHZN6l3MHEGe1g=
git.tebibyte.media/tomo/tomo v0.31.0 h1:LHPpj3AWycochnC8F441aaRNS6Tq6w6WnBrp/LGjyhM=
git.tebibyte.media/tomo/tomo v0.31.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
git.tebibyte.media/tomo/typeset v0.7.0 h1:JFpEuGmN6R2XSCvkINYxpH0AyYUqqs+dZYr6OSd91y0=
git.tebibyte.media/tomo/typeset v0.7.0/go.mod h1:PwDpSdBF3l/EzoIsa2ME7QffVVajnTHZN6l3MHEGe1g=
git.tebibyte.media/tomo/x v0.7.0 h1:HiLbKRWwwR+D1lYruhK3Z63JPiMlcISBe9TtCkZTeBI=
git.tebibyte.media/tomo/x v0.7.0/go.mod h1:h4vXFU+ZQETr7hxr/ydHqM1xFzHKvV2uKnmGzagWgnY=
git.tebibyte.media/tomo/xdg v0.1.0 h1:6G2WYPPiM2IXleCpKKHuJA34BxumwNWuLsUoX3yu5zA=
git.tebibyte.media/tomo/xdg v0.1.0/go.mod h1:tuaRwRkyYW7mqlxA7P2+V+e10KzcamNoUzcOgaIYKAY=
git.tebibyte.media/tomo/xgbkb v1.0.1 h1:b3HDUopjdQp1MZrb5Vpil4bOtk3NnNXtfQW27Blw2kE=
@ -15,12 +13,10 @@ github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJ
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 h1:Pf/0BAbppEOq4azPH6fnvUX2dycAwZdGkdxFn25j44c=
github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0/go.mod h1:AHecLyFNy6AN9f/+0AH/h1MI7X1+JL5bmCz4XlVZk7Y=
github.com/jezek/xgbutil v0.0.0-20231116234834-47f30c120111 h1:cX/mTy4LgFtWqr5dCadtdJ4zdh/KtPco5yFLsliaFyU=
github.com/jezek/xgbutil v0.0.0-20231116234834-47f30c120111/go.mod h1:AHecLyFNy6AN9f/+0AH/h1MI7X1+JL5bmCz4XlVZk7Y=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,455 +0,0 @@
package fallbackIcons
import "bytes"
import "image"
import _ "embed"
import _ "image/png"
import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/canvas"
//go:embed assets/icons-small.png
var atlasSmallBytes []byte
//go:embed assets/icons-large.png
var atlasLargeBytes []byte
func generateSource (data []byte, width int) map[tomo.Icon] canvas.Texture {
atlasImage, _, err := image.Decode(bytes.NewReader(data))
if err != nil { panic(err) }
atlasTexture := tomo.NewTexture(atlasImage)
source := make(map[tomo.Icon] canvas.Texture)
x := 0
y := 0
row := func () {
x = 0
y ++
}
col := func (id tomo.Icon) {
source[id] = atlasTexture.SubTexture(image.Rect (
x * width,
y * width,
(x + 1) * width,
(y + 1) * width))
x++
}
col(tomo.IconUnknown)
col(tomo.Icon("File"))
row()
// actions
col(tomo.IconAddressBookNew)
col(tomo.IconApplicationExit)
col(tomo.IconAppointmentNew)
col(tomo.IconCallStart)
col(tomo.IconCallStop)
col(tomo.IconContactNew)
// actions: dialog
col(tomo.IconDialogOkay)
col(tomo.IconDialogCancel)
// actions: edit
col(tomo.IconEditClear)
col(tomo.IconEditCopy)
col(tomo.IconEditCut)
col(tomo.IconEditDelete)
col(tomo.IconEditFind)
col(tomo.IconEditFindReplace)
col(tomo.IconEditPaste)
col(tomo.IconEditRedo)
col(tomo.IconEditSelectAll)
col(tomo.IconEditUndo)
// actions: file
col(tomo.IconFileNew)
col(tomo.IconDirectoryNew)
col(tomo.IconFileOpen)
col(tomo.IconFileOpenRecent)
col(tomo.IconFilePageSetup)
col(tomo.IconFilePrint)
col(tomo.IconFilePrintPreview)
col(tomo.IconFilePermissions)
col(tomo.IconFileProperties)
col(tomo.IconFileRename)
col(tomo.IconFileRevert)
col(tomo.IconFileSave)
col(tomo.IconFileSaveAs)
col(tomo.IconFileSend)
row()
// actions: format
col(tomo.IconFormatIndentLess)
col(tomo.IconFormatIndentMore)
col(tomo.IconFormatAlignCenter)
col(tomo.IconFormatAlignEven)
col(tomo.IconFormatAlignLeft)
col(tomo.IconFormatAlignRight)
col(tomo.IconFormatTextDirectionLtr)
col(tomo.IconFormatTextDirectionRtl)
col(tomo.IconFormatTextBold)
col(tomo.IconFormatTextItalic)
col(tomo.IconFormatTextUnderline)
col(tomo.IconFormatTextStrikethrough)
// actions: go
col(tomo.IconGoBottom)
col(tomo.IconGoDown)
col(tomo.IconGoFirst)
col(tomo.IconGoHome)
col(tomo.IconGoJump)
col(tomo.IconGoLast)
col(tomo.IconGoNext)
col(tomo.IconGoPrevious)
col(tomo.IconGoTop)
col(tomo.IconGoUp)
// actions: help
col(tomo.IconHelpAbout)
col(tomo.IconHelpContents)
col(tomo.IconHelpFaq)
// actions: insert
col(tomo.IconInsertImage)
col(tomo.IconInsertLink)
col(tomo.IconInsertObject)
col(tomo.IconInsertText)
// actions: list
col(tomo.IconListAdd)
col(tomo.IconListRemove)
row()
// actions: mail
col(tomo.IconMailForward)
col(tomo.IconMailMarkImportant)
col(tomo.IconMailMarkJunk)
col(tomo.IconMailMarkNotJunk)
col(tomo.IconMailMarkRead)
col(tomo.IconMailMarkUnread)
col(tomo.IconMailMessageNew)
col(tomo.IconMailReplyAll)
col(tomo.IconMailReplySender)
col(tomo.IconMailSend)
col(tomo.IconMailReceive)
// actions: media
col(tomo.IconMediaEject)
col(tomo.IconMediaPlaybackPause)
col(tomo.IconMediaPlaybackStart)
col(tomo.IconMediaPlaybackStop)
col(tomo.IconMediaRecord)
col(tomo.IconMediaSeekBackward)
col(tomo.IconMediaSeekForward)
col(tomo.IconMediaSkipBackward)
col(tomo.IconMediaSkipForward)
// actions: object
col(tomo.IconObjectFlipHorizontal)
col(tomo.IconObjectFlipVertical)
col(tomo.IconObjectRotateLeft)
col(tomo.IconObjectRotateRight)
// actions: process
col(tomo.IconProcessStop)
// actions: system
col(tomo.IconSystemLockScreen)
col(tomo.IconSystemLogOut)
col(tomo.IconSystemRun)
col(tomo.IconSystemSearch)
col(tomo.IconSystemReboot)
col(tomo.IconSystemShutdown)
row()
// actions: tools
col(tomo.IconToolsCheckSpelling)
// actions: value
col(tomo.IconValueIncrement)
col(tomo.IconValueDecrement)
col(tomo.IconValueReset)
// actions: view
col(tomo.IconViewFullscreen)
col(tomo.IconViewRefresh)
col(tomo.IconViewRestore)
col(tomo.IconViewSortAscending)
col(tomo.IconViewSortDescending)
// actions: window
col(tomo.IconWindowClose)
col(tomo.IconWindowNew)
// actions: zoom
col(tomo.IconZoomFitBest)
col(tomo.IconZoomIn)
col(tomo.IconZoomOriginal)
col(tomo.IconZoomOut)
row()
// applications
// Keep these in sync with nasin.ApplicationRole!
col(tomo.IconApplication)
col(tomo.IconApplicationWebBrowser)
col(tomo.IconApplicationMesssanger)
col(tomo.IconApplicationPhone)
col(tomo.IconApplicationMail)
col(tomo.IconApplicationTerminalEmulator)
col(tomo.IconApplicationFileBrowser)
col(tomo.IconApplicationTextEditor)
col(tomo.IconApplicationDocumentViewer)
col(tomo.IconApplicationWordProcessor)
col(tomo.IconApplicationSpreadsheet)
col(tomo.IconApplicationSlideshow)
col(tomo.IconApplicationCalculator)
col(tomo.IconApplicationPreferences)
col(tomo.IconApplicationProcessManager)
col(tomo.IconApplicationSystemInformation)
col(tomo.IconApplicationManual)
col(tomo.IconApplicationCamera)
col(tomo.IconApplicationImageViewer)
col(tomo.IconApplicationMediaPlayer)
col(tomo.IconApplicationImageEditor)
col(tomo.IconApplicationAudioEditor)
col(tomo.IconApplicationVideoEditor)
col(tomo.IconApplicationClock)
col(tomo.IconApplicationCalendar)
col(tomo.IconApplicationChecklist)
row()
// categories: applications
col(tomo.IconApplications)
col(tomo.IconApplicationsAccessories)
col(tomo.IconApplicationsDevelopment)
col(tomo.IconApplicationsEngineering)
col(tomo.IconApplicationsGames)
col(tomo.IconApplicationsGraphics)
col(tomo.IconApplicationsInternet)
col(tomo.IconApplicationsMultimedia)
col(tomo.IconApplicationsOffice)
col(tomo.IconApplicationsScience)
col(tomo.IconApplicationsSystem)
col(tomo.IconApplicationsUtilities)
// categories: preferences
col(tomo.IconPreferences)
col(tomo.IconPreferencesDesktop)
col(tomo.IconPreferencesPeripherals)
col(tomo.IconPreferencesPersonal)
col(tomo.IconPreferencesSystem)
col(tomo.IconPreferencesNetwork)
row()
// devices
col(tomo.IconDevice)
col(tomo.IconDeviceCamera)
col(tomo.IconDeviceWebCamera)
col(tomo.IconDeviceComputer)
col(tomo.IconDevicePda)
col(tomo.IconDevicePhone)
col(tomo.IconDevicePrinter)
col(tomo.IconDeviceScanner)
col(tomo.IconDeviceMultimediaPlayer)
col(tomo.IconDeviceVideoDisplay)
col(tomo.IconDeviceAudioInput)
col(tomo.IconDeviceAudioOutput)
// devices: hardware
col(tomo.IconHardware)
col(tomo.IconHardwareCPU)
col(tomo.IconHardwareGPU)
col(tomo.IconHardwareRAM)
col(tomo.IconHardwareSoundCard)
col(tomo.IconHardwareNetworkAdapter)
// devices: power
col(tomo.IconPowerBattery)
// devices: storage
col(tomo.IconStorageHardDisk)
col(tomo.IconStorageFloppyDisk)
col(tomo.IconStorageSolidState)
col(tomo.IconStorageOptical)
col(tomo.IconStorageFlashStick)
col(tomo.IconStorageFlashCard)
col(tomo.IconStorageMagneticTape)
// devices: input
col(tomo.IconInputGaming)
col(tomo.IconInputKeyboard)
col(tomo.IconInputMouse)
col(tomo.IconInputTablet)
row()
// devices: network
col(tomo.IconNetworkWired)
col(tomo.IconNetworkWireless)
col(tomo.IconNetworkCellular)
col(tomo.IconNetworkLocal)
col(tomo.IconNetworkInternet)
col(tomo.IconNetworkVPN)
col(tomo.IconNetworkServer)
col(tomo.IconNetworkWorkgroup)
row()
// emblems
col(tomo.IconEmblemDefault)
col(tomo.IconEmblemEncrypted)
col(tomo.IconEmblemFavorite)
col(tomo.IconEmblemImportant)
col(tomo.IconEmblemReadOnly)
col(tomo.IconEmblemShared)
col(tomo.IconEmblemSymbolicLink)
col(tomo.IconEmblemSynchronized)
col(tomo.IconEmblemSystem)
col(tomo.IconEmblemUnreadable)
row()
// places
col(tomo.IconPlaceDirectory)
col(tomo.IconPlaceRemote)
col(tomo.IconPlaceHome)
col(tomo.IconPlaceDownloads)
col(tomo.IconPlaceDesktop)
col(tomo.IconPlacePhotos)
col(tomo.IconPlaceBooks)
col(tomo.IconPlaceBookmarks)
col(tomo.IconPlaceTrash)
col(tomo.IconPlaceDocuments)
col(tomo.IconPlaceRepositories)
col(tomo.IconPlaceMusic)
col(tomo.IconPlaceArchives)
col(tomo.IconPlaceFonts)
col(tomo.IconPlaceBinaries)
col(tomo.IconPlaceVideos)
col(tomo.IconPlace3DObjects)
col(tomo.IconPlaceHistory)
col(tomo.IconPlacePreferences)
row()
// status: checkbox
col(tomo.IconCheckboxChecked)
col(tomo.IconCheckboxUnchecked)
// status: appointments
col(tomo.IconAppointmentMissed)
col(tomo.IconAppointmentSoon)
// status: dialogs
col(tomo.IconDialogError)
col(tomo.IconDialogInformation)
col(tomo.IconDialogPassword)
col(tomo.IconDialogQuestion)
col(tomo.IconDialogWarning)
// status: directories
col(tomo.IconDirectoryDragAccept)
col(tomo.IconDirectoryFull)
col(tomo.IconDirectoryOpen)
col(tomo.IconDirectoryVisiting)
// status: trash
col(tomo.IconTrashFull)
// status: resource
col(tomo.IconResourceLoading)
col(tomo.IconResourceMissing)
// status: mail
col(tomo.IconMailAttachment)
col(tomo.IconMailUnread)
col(tomo.IconMailReplied)
col(tomo.IconMailSigned)
col(tomo.IconMailSignedVerified)
row()
// status: network
col(tomo.IconCellularSignal0)
col(tomo.IconCellularSignal1)
col(tomo.IconCellularSignal2)
col(tomo.IconCellularSignal3)
col(tomo.IconWirelessSignal0)
col(tomo.IconWirelessSignal1)
col(tomo.IconWirelessSignal2)
col(tomo.IconWirelessSignal3)
col(tomo.IconNetworkError)
col(tomo.IconNetworkIdle)
col(tomo.IconNetworkOffline)
col(tomo.IconNetworkReceive)
col(tomo.IconNetworkTransmit)
col(tomo.IconNetworkTransmitReceive)
// status: print
col(tomo.IconPrintError)
col(tomo.IconPrintPrinting)
// status: security
col(tomo.IconSecurityHigh)
col(tomo.IconSecurityMedium)
col(tomo.IconSecurityLow)
// status: software
col(tomo.IconSoftwareUpdateAvailable)
col(tomo.IconSoftwareUpdateUrgent)
col(tomo.IconSoftwareInstalling)
// status: sync
col(tomo.IconSyncError)
col(tomo.IconSyncSynchronizing)
// status: tasks
col(tomo.IconTaskDue)
col(tomo.IconTaskPastDue)
// status: users
col(tomo.IconUserAvailable)
col(tomo.IconUserAway)
col(tomo.IconUserIdle)
col(tomo.IconUserOffline)
row()
// status: power
col(tomo.IconBattery0)
col(tomo.IconBattery1)
col(tomo.IconBattery2)
col(tomo.IconBattery3)
col(tomo.IconBrightness0)
col(tomo.IconBrightness1)
col(tomo.IconBrightness2)
col(tomo.IconBrightness3)
// status: media
col(tomo.IconVolume0)
col(tomo.IconVolume1)
col(tomo.IconVolume2)
col(tomo.IconVolume3)
col(tomo.IconPlaylistRepeat)
col(tomo.IconPlaylistShuffle)
// status: weather
col(tomo.IconWeatherClear)
col(tomo.IconWeatherClearNight)
col(tomo.IconWeatherFewClouds)
col(tomo.IconWeatherFewCloudsNight)
col(tomo.IconWeatherFog)
col(tomo.IconWeatherOvercast)
col(tomo.IconWeatherSevereAlert)
col(tomo.IconWeatherShowers)
col(tomo.IconWeatherShowersScattered)
col(tomo.IconWeatherSnow)
col(tomo.IconWeatherStorm)
return source
}
type iconTheme struct {
texturesSmall map[tomo.Icon] canvas.Texture
texturesLarge map[tomo.Icon] canvas.Texture
}
// New creates a new fallback icon theme.
func New () tomo.Icons {
return new(iconTheme)
}
func (this *iconTheme) ensure () {
if this.texturesSmall != nil { return }
this.texturesSmall = generateSource(atlasSmallBytes, 16)
this.texturesLarge = generateSource(atlasLargeBytes, 32)
}
func (this *iconTheme) selectSource (size tomo.IconSize) map[tomo.Icon] canvas.Texture {
if size == tomo.IconSizeSmall {
return this.texturesSmall
} else {
return this.texturesLarge
}
}
func (this *iconTheme) Icon (icon tomo.Icon, size tomo.IconSize) canvas.Texture {
this.ensure()
source := this.selectSource(size)
if texture, ok := source[icon]; ok {
return texture
}
return nil
}
func (this *iconTheme) MimeIcon (mime data.Mime, size tomo.IconSize) canvas.Texture {
this.ensure()
source := this.selectSource(size)
if mime == data.M("inode", "directory") {
return source[tomo.IconPlaceDirectory]
} else {
return source[tomo.Icon("File")]
}
}

View File

@ -1,182 +0,0 @@
package xdgIcons
import "os"
import "fmt"
import "log"
import "image"
import "regexp"
import "strings"
import _ "image/png"
import "git.tebibyte.media/tomo/tomo"
import xdgIconTheme "git.tebibyte.media/tomo/xdg/icon-theme"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/canvas"
type iconTheme struct {
xdg xdgIconTheme.Theme
fallback tomo.Icons
texturesSmall map[tomo.Icon] canvas.Texture
texturesMedium map[tomo.Icon] canvas.Texture
texturesLarge map[tomo.Icon] canvas.Texture
}
func FindThemeWarn (name string, fallback tomo.Icons, path ...string) (tomo.Icons, error) {
this := &iconTheme {
fallback: fallback,
texturesLarge: make(map[tomo.Icon] canvas.Texture),
texturesMedium: make(map[tomo.Icon] canvas.Texture),
texturesSmall: make(map[tomo.Icon] canvas.Texture),
}
xdg, err := xdgIconTheme.FindThemeWarn(name, path...)
if err != nil { return nil, err }
this.xdg = xdg
return this, nil
}
func (this *iconTheme) selectSource (size tomo.IconSize) map[tomo.Icon] canvas.Texture {
switch size {
case tomo.IconSizeMedium: return this.texturesMedium
case tomo.IconSizeLarge: return this.texturesLarge
default: return this.texturesSmall
}
}
func (this *iconTheme) xdgIcon (name string, size tomo.IconSize) (canvas.Texture, bool) {
// TODO use scaling factor instead of 1
// find icon file
icon, err := this.xdg.FindIcon(name, iconSizePixels(size), 1, xdgIconTheme.PNG)
if err != nil { return nil, false }
// open icon file
iconFile, err := os.Open(icon.Path)
if err != nil {
// this failing indicates a broken icon theme
log.Printf("nasin: icon file '%s' is inaccessible: %v\n", icon.Path, err)
return nil, false
}
iconImage, _, err := image.Decode(iconFile)
if err != nil {
// this failing indicates a broken icon theme
log.Printf("nasin: icon file '%s' is broken: %v\n", icon.Path, err)
return nil, false
}
return tomo.NewTexture(iconImage), true
}
func (this *iconTheme) Icon (icon tomo.Icon, size tomo.IconSize) canvas.Texture {
source := this.selectSource(size)
texture, ok := source[icon]
if !ok {
texture = this.icon(icon, size)
source[icon] = texture
}
if texture == nil {
return this.fallback.Icon(icon, size)
} else {
return texture
}
}
func (this *iconTheme) MimeIcon (mime data.Mime, size tomo.IconSize) canvas.Texture {
icon := tomo.Icon(mime.String())
source := this.selectSource(size)
texture, ok := source[icon]
if !ok {
texture = this.mimeIcon(mime, size)
source[icon] = texture
}
if texture == nil {
return this.fallback.MimeIcon(mime, size)
} else {
return texture
}
}
func (this *iconTheme) icon (icon tomo.Icon, size tomo.IconSize) canvas.Texture {
if texture, ok := this.xdgIcon(XdgIconName(icon), size); ok {
return texture
}
if texture, ok := this.xdgIcon(XdgIconName(generalizeIcon(icon)), size); ok {
return texture
}
return nil
}
func (this *iconTheme) mimeIcon (mime data.Mime, size tomo.IconSize) canvas.Texture {
if texture, ok := this.xdgIcon(xdgFormatMime(mime), size); ok {
return texture
}
if texture, ok := this.xdgIcon(xdgFormatMime(generalizeMimeType(mime)), size); ok {
return texture
}
if texture, ok := this.xdgIcon(xdgFormatMime(data.M("text", "x-generic")), size); ok {
return texture
}
return nil
}
var kebabMatchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
var kebabMatchAllCaps = regexp.MustCompile("([a-z0-9])([A-Z])")
// XdgIconName returns the best XDG name for the given icon.
func XdgIconName (icon tomo.Icon) string {
if name, ok := xdgIconNames[icon]; ok {
return name
}
name := kebabMatchFirstCap.ReplaceAllString(string(icon), "${1}-${2}")
name = kebabMatchAllCaps.ReplaceAllString(string(name), "${1}-${2}")
return strings.ToLower(name)
}
func generalizeIcon (icon tomo.Icon) tomo.Icon {
name := string(icon)
switch {
case strings.HasPrefix(name, "Application"): return tomo.IconApplication
case strings.HasPrefix(name, "Preferences"): return tomo.IconPreferences
case strings.HasPrefix(name, "Device"): return tomo.IconDevice
case strings.HasPrefix(name, "Hardware"): return tomo.IconHardware
case strings.HasPrefix(name, "Storage"): return tomo.IconStorageHardDisk
case strings.HasPrefix(name, "Input"): return tomo.IconInputMouse
case strings.HasPrefix(name, "Network"): return tomo.IconNetworkWired
case strings.HasPrefix(name, "Place"): return tomo.IconPlaceDirectory
case strings.HasPrefix(name, "Directory"): return tomo.IconPlaceDirectory
case strings.HasPrefix(name, "Trash"): return tomo.IconPlaceTrash
case strings.HasPrefix(name, "Help"): return tomo.IconHelpContents
}
switch icon {
case tomo.IconCellularSignal0: return tomo.IconWirelessSignal0
case tomo.IconCellularSignal1: return tomo.IconWirelessSignal1
case tomo.IconCellularSignal2: return tomo.IconWirelessSignal2
case tomo.IconCellularSignal3: return tomo.IconWirelessSignal3
}
return icon
}
func xdgFormatMime (mime data.Mime) string {
return fmt.Sprintf("%s-%s", mime.Type, mime.Subtype)
}
func generalizeMimeType (mime data.Mime) data.Mime {
// FIXME make this more accurate
// https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
mime.Subtype = "x-generic"
return mime
}
func iconSizePixels (size tomo.IconSize) int {
// TODO: once Tomo has scaling support, take that into account here
switch size {
case tomo.IconSizeMedium: return 24
case tomo.IconSizeLarge: return 48
default: return 16
}
}

View File

@ -1,136 +0,0 @@
package xdgIcons
import "git.tebibyte.media/tomo/tomo"
// icons that can't be directly translated with regex
var xdgIconNames = map[tomo.Icon] string {
tomo.IconUnknown: "image-missing",
tomo.IconFileNew: "document-new",
tomo.IconDirectoryNew: "folder-new",
tomo.IconFileOpen: "document-open",
tomo.IconFileOpenRecent: "document-open-recent",
tomo.IconFilePageSetup: "document-page-setup",
tomo.IconFilePrint: "document-print",
tomo.IconFilePrintPreview: "document-print-preview",
tomo.IconFilePermissions: "document-permissions", // non-standard
tomo.IconFileProperties: "document-properties",
tomo.IconFileRename: "document-rename", // non-standard
tomo.IconFileRevert: "document-revert",
tomo.IconFileSave: "document-save",
tomo.IconFileSaveAs: "document-save-as",
tomo.IconFileSend: "document-send",
tomo.IconFormatAlignCenter: "format-justify-center",
tomo.IconFormatAlignEven: "format-justify-fill",
tomo.IconFormatAlignLeft: "format-justify-left",
tomo.IconFormatAlignRight: "format-justify-right",
tomo.IconMailReceive: "mail-send-receive",
tomo.IconValueIncrement: "list-add",
tomo.IconValueDecrement: "list-remove",
tomo.IconValueReset: "value-reset", // non-standard
tomo.IconApplication: "system-run",
tomo.IconApplicationWebBrowser: "web-browser",
tomo.IconApplicationMesssanger: "internet-messanger", // non-standard
tomo.IconApplicationPhone: "accessories-phone", // non-standard
tomo.IconApplicationMail: "internet-mail-client", // non-standard
tomo.IconApplicationTerminalEmulator: "utilities-terminal",
tomo.IconApplicationFileBrowser: "system-file-manager",
tomo.IconApplicationTextEditor: "accessories-text-editor",
tomo.IconApplicationDocumentViewer: "office-document-viewer", // non-standard
tomo.IconApplicationWordProcessor: "office-word-processor", // non-standard
tomo.IconApplicationSpreadsheet: "office-spreadsheet", // non-standard
tomo.IconApplicationSlideshow: "office-slideshow", // non-standard
tomo.IconApplicationCalculator: "accessories-calculator",
tomo.IconApplicationPreferences: "preferences-system",
tomo.IconApplicationProcessManager: "utilities-system-monitor",
tomo.IconApplicationSystemInformation: "distributor-logo", // non-standard
tomo.IconApplicationManual: "help-browser",
tomo.IconApplicationCamera: "accessories-camera", // non-standard
tomo.IconApplicationImageViewer: "graphics-image-viewer", // non-standard
tomo.IconApplicationMediaPlayer: "audio-video-media-player", // non-standard
tomo.IconApplicationImageEditor: "graphics-image-editor", // non-standard
tomo.IconApplicationAudioEditor: "audio-audio-editor", // non-standard
tomo.IconApplicationVideoEditor: "video-video-editor", // non-standard
tomo.IconApplicationClock: "accessories-clock", // non-standard
tomo.IconApplicationCalendar: "accessories-calendar", // non-standard
tomo.IconApplicationChecklist: "accessories-checklist", // non-standard
tomo.IconApplications: "applications-other",
tomo.IconPreferences: "preferences-other",
tomo.IconPreferencesNetwork: "preferences-system-network",
tomo.IconDevice: "device", // non-standard
tomo.IconDeviceCamera: "camera-photo",
tomo.IconDeviceWebCamera: "camera-web",
tomo.IconDeviceComputer: "computer",
tomo.IconDevicePda: "pda",
tomo.IconDevicePhone: "phone",
tomo.IconDevicePrinter: "printer",
tomo.IconDeviceScanner: "scanner",
tomo.IconDeviceMultimediaPlayer: "multimedia-player",
tomo.IconDeviceVideoDisplay: "video-display",
tomo.IconDeviceAudioInput: "audio-input-microphone",
tomo.IconDeviceAudioOutput: "audio-speakers",
tomo.IconHardware: "card", // non-standard
tomo.IconHardwareCPU: "cpu",
tomo.IconHardwareGPU: "video-card",
tomo.IconHardwareRAM: "ram",
tomo.IconHardwareSoundCard: "audio-card",
tomo.IconHardwareNetworkAdapter: "network-card",
tomo.IconPowerBattery: "battery",
tomo.IconStorageHardDisk: "drive-harddisk",
tomo.IconStorageFloppyDisk: "media-floppy",
tomo.IconStorageSolidState: "drive-solid",
tomo.IconStorageOptical: "media-optical",
tomo.IconStorageFlashStick: "media-removable",
tomo.IconStorageFlashCard: "media-flash",
tomo.IconStorageMagneticTape: "media-tape",
tomo.IconEmblemReadOnly: "emblem-readonly",
tomo.IconPlaceDirectory: "folder",
tomo.IconPlaceRemote: "folder-remote",
tomo.IconPlaceHome: "user-home",
tomo.IconPlaceDownloads: "folder-downloads", // common
tomo.IconPlaceDesktop: "user-desktop",
tomo.IconPlacePhotos: "folder-pictures", // common
tomo.IconPlaceBooks: "folder-books", // non-standard
tomo.IconPlaceBookmarks: "user-bookmarks",
tomo.IconPlaceTrash: "user-trash",
tomo.IconPlaceDocuments: "folder-documents", // common
tomo.IconPlaceRepositories: "folder-repositories", // non-standard
tomo.IconPlaceMusic: "folder-music", // common
tomo.IconPlaceArchives: "folder-archives", // non-standard
tomo.IconPlaceFonts: "folder-fonts", // non-standard
tomo.IconPlaceBinaries: "folder-executables", // non-standard
tomo.IconPlaceVideos: "folder-videos", // common
tomo.IconPlace3DObjects: "folder-3d-objects", // non-standard
tomo.IconPlaceHistory: "folder-history", // non-standard
tomo.IconPlacePreferences: "preferences-other",
tomo.IconDirectoryDragAccept: "folder-drag-accept",
tomo.IconDirectoryFull: "folder-full",
tomo.IconDirectoryOpen: "folder-open",
tomo.IconDirectoryVisiting: "folder-visiting",
tomo.IconTrashFull: "user-trash-full",
tomo.IconResourceLoading: "image-loading",
tomo.IconResourceMissing: "image-missing",
tomo.IconCellularSignal0: "nm-signal-00", // common
tomo.IconCellularSignal1: "nm-signal-25", // common
tomo.IconCellularSignal2: "nm-signal-75", // common
tomo.IconCellularSignal3: "nm-signal-100", // common
tomo.IconWirelessSignal0: "wifi-signal-00", // common
tomo.IconWirelessSignal1: "wifi-signal-25", // common
tomo.IconWirelessSignal2: "wifi-signal-75", // common
tomo.IconWirelessSignal3: "wifi-signal-100", // common
tomo.IconPrintError: "printer-error",
tomo.IconPrintPrinting: "printer-printing",
tomo.IconBattery0: "battery-caution",
tomo.IconBattery1: "battery-low",
tomo.IconBattery2: "battery-good", // common
tomo.IconBattery3: "battery-full", // common
tomo.IconBrightness0: "brightness-dim", // non-standard
tomo.IconBrightness1: "brightness-medium", // non-standard
tomo.IconBrightness2: "brightness-bright", // non-standard
tomo.IconBrightness3: "brightness-full", // non-standard
tomo.IconVolume0: "audio-volume-muted",
tomo.IconVolume1: "audio-volume-low",
tomo.IconVolume2: "audio-volume-medium",
tomo.IconVolume3: "audio-volume-high",
tomo.IconPlaylistRepeat: "media-playlist-repeat",
tomo.IconPlaylistShuffle: "media-playlist-shuffle",
}

View File

@ -1,2 +0,0 @@
// Package registrar provides platform-dependent components at compile time.
package registrar

View File

@ -1,43 +0,0 @@
//go:build unix && (!darwin)
package registrar
import "os"
import "log"
import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/backend/x"
import "git.tebibyte.media/tomo/nasin/internal/style"
import "git.tebibyte.media/tomo/nasin/internal/icons/xdg"
import "git.tebibyte.media/tomo/nasin/internal/icons/fallback"
import "git.tebibyte.media/tomo/nasin/internal/style/fallback"
import "git.tebibyte.media/tomo/nasin/internal/style/aluminum"
func RegisterBackend () error {
tomo.Register(1, x.New)
return nil
}
func SetTheme () error {
var styl *style.Style
// TODO eventually get rid of this when we make a file format for
// storing visual styles
if os.Getenv("TOMO_USE_ALUMINUM_STYLE") != "" {
styl = aluminumStyle.New()
} else {
styl = fallbackStyle.New()
}
icons := fallbackIcons.New()
iconThemeName := os.Getenv("TOMO_XDG_ICON_THEME")
if iconThemeName != "" {
xdgIconTheme, err := xdgIcons.FindThemeWarn(iconThemeName, icons)
if err == nil {
icons = xdgIconTheme
} else {
log.Printf("nasin: could not load icon theme '%s': %v", iconThemeName, err)
}
}
tomo.SetStyle(styl)
tomo.SetIcons(icons)
return nil
}

2
internal/registry/doc.go Normal file
View File

@ -0,0 +1,2 @@
// Package registry provides platform-dependent components at compile time.
package registry

View File

@ -0,0 +1,13 @@
//go:build unix && (!darwin)
package registry
import "git.tebibyte.media/tomo/x"
import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/tomo/theme"
import "git.tebibyte.media/tomo/nasin/internal/theme/default"
func Init () error {
theme.SetTheme(defaultTheme.Theme())
tomo.Register(1, x.NewBackend)
return nil
}

View File

@ -1,19 +0,0 @@
package aluminumStyle
import "image/color"
import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/nasin/internal/style"
// New returns Aluminum, a futuristic, bluish-white style.
func New () *style.Style {
return &style.Style {
Colors: map[tomo.Color] color.Color {
tomo.ColorBackground: colorBackground,
tomo.ColorForeground: colorForeground,
tomo.ColorRaised: colorRaised,
tomo.ColorSunken: colorSunken,
tomo.ColorAccent: colorFocus,
},
Rules: rules,
}
}

View File

@ -1,392 +0,0 @@
package aluminumStyle
import "image/color"
import "git.tebibyte.media/tomo/tomo"
import "golang.org/x/image/font/basicfont"
import "git.tebibyte.media/tomo/nasin/internal/style"
func hex (color uint32) (c color.RGBA) {
c.A = uint8(color)
c.B = uint8(color >> 8)
c.G = uint8(color >> 16)
c.R = uint8(color >> 24)
return
}
func border (top, right, bottom, left uint32, width ...int) tomo.Border {
return tomo.Border {
Width: tomo.I(width...),
Color: [4]color.Color {
hex(top), hex(right),
hex(bottom), hex(left),
},
}
}
var colorDot = hex(0x7391c080)
var colorFocus = hex(0x5f8bc4FF)
var colorHighlight = hex(0x5f8bc4FF)
var colorBackground = hex(0xd4d4d4FF)
var colorForeground = color.Black
var colorOutline = color.Black
var colorGutter = hex(0xbfc6d1FF)
var colorGutterHovered = hex(0xc5cbd6FF)
var colorRaised = hex(0xe9eaeaFF)
var colorRaisedPressed = hex(0xccd4ddFF)
var colorRaisedFocused = hex(0xcfd6ddFF)
var colorRaisedHovered = hex(0xf1f3f5FF)
var colorSunken = hex(0xe9eaeaFF)
var colorSunkenFocused = hex(0xe0e6eeFF)
var colorSunkenPressed = hex(0xe0e6eeFF)
var colorCalendarWeekdayHeader = hex(0xd3cac2FF)
var colorCalendarWeekend = hex(0xc2d3c4FF)
var colorCalendarDay = hex(0xd6dae2FF)
var outline = tomo.Border {
Width: tomo.I(1),
Color: [4]color.Color {
colorOutline,
colorOutline,
colorOutline,
colorOutline,
},
}
var borderEngraved = border(0xc3c3c5FF, 0xe3e3e3FF, 0xe9e9e9ff, 0xc2c2c2ff, 1)
var borderGap = border(0x697c7cFF, 0x566767FF, 0x566767ff, 0x697c7cff, 1)
var borderLifted = border(0xf9fafcFF, 0xc2c8d3FF, 0xa4afc0ff, 0xf5f6f8ff, 1)
var borderLiftedFocused = border(0xf0f4f9FF, 0xb1bacaFF, 0x9aa6b7ff, 0xe4e9eeff, 1)
var borderFocused = border(0x5f8bc4FF, 0x5f8bc4FF, 0x5f8bc4ff, 0x5f8bc4ff, 1)
var borderTear = borderEngraved
var borderTearFocused = border(0x7f94b5FF, 0xced7e4FF, 0xced7e4FF, 0x7f94b5FF, 1)
var borderTearPad = border(0x00000000, 0x00000000, 0x00000000, 0x00000000, 3)
var borderTearPadFocused = border(0x7391c080, 0x7391c080, 0x7391c080, 0x7391c080, 3)
var borderInnerShadow = border(0xa4afc0FF, 0xa4afc0FF, 0xa4afc0ff, 0xa4afc0ff, 1, 0, 0, 1)
var borderOuterShadow = border(0xa4afc0FF, 0xa4afc0FF, 0xa4afc0ff, 0xa4afc0ff, 0, 1, 1, 0)
var rules = []style.Rule {
// *.*[*]
style.Rule {
Default: style.AS (
style.AttrFace { Face: basicfont.Face7x13 },
style.AttrTextColor { Color: tomo.ColorForeground },
style.AttrDotColor { Color: colorDot },
style.AttrGap { X: 8, Y: 8 },
),
},
// *.Button[*]
style.Rule {
Role: tomo.R("", "Button", ""),
Default: style.AS (
style.AttrBorder {
borderEngraved,
borderGap,
borderLifted,
},
style.AttrPadding(tomo.I(4, 8)),
style.AttrColor { Color: tomo.ColorRaised },
),
Pressed: style.AS (
style.AttrBorder {
borderEngraved,
borderGap,
borderInnerShadow,
},
style.AttrPadding(tomo.I(5, 8, 4, 9)),
style.AttrColor { Color: colorRaisedPressed },
),
Focused: style.AS (
style.AttrBorder {
borderEngraved,
borderGap,
borderLiftedFocused,
},
style.AttrPadding(tomo.I(4, 8)),
style.AttrColor { Color: colorRaisedFocused },
),
Hovered: style.AS (
style.AttrColor { Color: colorRaisedHovered },
),
},
// *.TextInput[*]
style.Rule {
Role: tomo.R("", "TextInput", ""),
Default: style.AS (
style.AttrBorder {
borderEngraved,
borderGap,
borderInnerShadow,
},
style.AttrColor { Color: tomo.ColorSunken },
style.AttrPadding(tomo.I(5, 4, 4, 5)),
),
Focused: style.AS (
style.AttrBorder {
borderEngraved,
borderFocused,
borderInnerShadow,
},
),
},
// *.TextView[*]
style.Rule {
Role: tomo.R("", "TextView", ""),
Default: style.AS (
style.AttrBorder {
borderEngraved,
borderGap,
borderInnerShadow,
},
style.AttrColor { Color: tomo.ColorSunken },
style.AttrPadding(tomo.I(8)),
),
},
// *.NumberInput[*]
style.Rule {
Role: tomo.R("", "NumberInput", ""),
Default: style.AS (
style.AttrGap { },
),
},
// *.Container[sunken]
style.Rule {
Role: tomo.R("", "Container", "sunken"),
Default: style.AS (
style.AttrBorder {
borderEngraved,
borderGap,
borderInnerShadow,
},
style.AttrColor { Color: tomo.ColorSunken },
style.AttrPadding(tomo.I(8)),
),
},
// *.Container[outer]
style.Rule {
Role: tomo.R("", "Container", "outer"),
Default: style.AS (
style.AttrColor { Color: tomo.ColorBackground },
style.AttrPadding(tomo.I(8)),
),
},
// *.Container[menu]
style.Rule {
Role: tomo.R("", "Container", "menu"),
Default: style.AS (
style.AttrBorder {
borderGap,
borderLifted,
},
style.AttrColor { Color: tomo.ColorBackground },
style.AttrGap { },
),
},
// *.Heading[*]
style.Rule {
Role: tomo.R("", "Heading", ""),
Default: style.AS (
style.AttrAlign { X: tomo.AlignMiddle, Y: tomo.AlignMiddle },
),
},
// *.Separator[*]
style.Rule {
Role: tomo.R("", "Separator", ""),
Default: style.AS (
style.AttrBorder {
borderEngraved,
},
),
},
// *.Slider[*]
style.Rule {
Role: tomo.R("", "Slider", ""),
Default: style.AS (
style.AttrBorder {
borderEngraved,
borderGap,
borderInnerShadow,
},
style.AttrColor { Color: colorGutter },
),
Focused: style.AS (
style.AttrBorder {
borderEngraved,
borderFocused,
borderInnerShadow,
},
),
Hovered: style.AS (
style.AttrColor { Color: colorGutterHovered },
),
},
// *.Slider[horizontal]
style.Rule {
Role: tomo.R("", "Slider", "horizontal"),
Default: style.AS(style.AttrMinimumSize { X: 48 }),
},
// *.Slider[vertical]
style.Rule {
Role: tomo.R("", "Slider", "vertical"),
Default: style.AS(style.AttrMinimumSize { Y: 48 }),
},
// *.SliderHandle[*]
style.Rule {
Role: tomo.R("", "SliderHandle", ""),
Default: style.AS (
style.AttrBorder {
borderOuterShadow,
borderGap,
borderLifted,
},
style.AttrColor { Color: tomo.ColorRaised },
style.AttrMinimumSize { X: 12, Y: 12, },
),
},
// *.Checkbox[*]
style.Rule {
Role: tomo.R("", "Checkbox", ""),
Default: style.AS (
style.AttrBorder {
borderEngraved,
borderGap,
borderInnerShadow,
},
style.AttrColor { Color: tomo.ColorSunken },
style.AttrPadding(tomo.I(0, 1, 1, 0)),
style.AttrMinimumSize { X: 19, Y: 19 },
),
Focused: style.AS (
style.AttrBorder {
borderEngraved,
borderFocused,
borderInnerShadow,
},
style.AttrPadding(tomo.I(0)),
style.AttrColor { Color: colorSunkenFocused },
),
},
// *.LabelCheckbox[*]
style.Rule {
Role: tomo.R("", "LabelCheckbox", ""),
Default: style.AS (
style.AttrGap { X: 8, Y: 8 },
),
},
// *.MenuItem[*]
style.Rule {
Role: tomo.R("", "MenuItem", ""),
Default: style.AS (
style.AttrPadding(tomo.I(4)),
style.AttrGap { X: 4, Y: 4 },
style.AttrColor { Color: color.Transparent },
),
Hovered: style.AS (
style.AttrColor { Color: colorDot },
),
Focused: style.AS (
style.AttrColor { Color: colorDot },
),
},
// *.File[*]
style.Rule {
Role: tomo.R("", "File", ""),
Default: style.AS (
style.AttrColor { Color: color.Transparent },
),
Focused: style.AS (
style.AttrColor { Color: colorDot },
),
},
// *.TearLine[*]
style.Rule {
Role: tomo.R("", "TearLine", ""),
Default: style.AS (
style.AttrBorder {
borderTearPad,
borderTear,
},
),
Hovered: style.AS (
style.AttrBorder {
borderTearPadFocused,
borderTearFocused,
},
),
Focused: style.AS (
style.AttrBorder {
borderTearPadFocused,
borderTearFocused,
},
),
},
// *.Calendar[*]
style.Rule {
Role: tomo.R("", "Calendar", ""),
Default: style.AS (
style.AttrBorder {
borderOuterShadow,
borderGap,
},
style.AttrColor { Color: tomo.ColorRaised },
style.AttrPadding(tomo.I(2)),
style.AttrGap { X: 2, Y: 2 },
),
},
// *.CalendarGrid[*]
style.Rule {
Role: tomo.R("", "CalendarGrid", ""),
Default: style.AS (
style.AttrGap { X: 2, Y: 2 },
),
},
// *.CalendarWeekdayHeader[*]
style.Rule {
Role: tomo.R("", "CalendarWeekdayHeader", ""),
Default: style.AS (
style.AttrPadding(tomo.I(2)),
style.AttrColor { Color: colorCalendarWeekdayHeader },
),
},
// *.CalendarDay[weekday]
style.Rule {
Role: tomo.R("", "CalendarDay", "weekday"),
Default: style.AS (
style.AttrPadding(tomo.I(2)),
style.AttrMinimumSize { X: 32, Y: 32 },
style.AttrColor { Color: colorCalendarDay },
),
},
// *.CalendarDay[weekend]
style.Rule {
Role: tomo.R("", "CalendarDay", "weekend"),
Default: style.AS (
style.AttrPadding(tomo.I(2)),
style.AttrMinimumSize { X: 32, Y: 32 },
style.AttrColor { Color: colorCalendarWeekend },
),
},
}

View File

@ -1,558 +0,0 @@
package fallbackStyle
import "image/color"
import "git.tebibyte.media/tomo/tomo"
import "golang.org/x/image/font/basicfont"
import "git.tebibyte.media/tomo/nasin/internal/style"
var colorFocus = color.RGBA { R: 61, G: 128, B: 143, A: 255 }
var colorInput = color.RGBA { R: 208, G: 203, B: 150, A: 255 }
var colorCarved = color.RGBA { R: 151, G: 160, B: 150, A: 255 }
var colorGutter = color.RGBA { R: 116, G: 132, B: 126, A: 255 }
var colorShadow = color.RGBA { R: 57, G: 59, B: 57, A: 255 }
var colorShade = color.RGBA { A: 128 }
var colorInputShadow = color.RGBA { R: 143, G: 146, B: 91, A: 255 }
var colorHighlight = color.RGBA { R: 207, G: 215, B: 210, A: 255 }
var colorBackground = color.RGBA { R: 169, G: 171, B: 168, A: 255 }
var colorCarvedPressed = color.RGBA { R: 129, G: 142, B: 137, A: 255 }
var colorForeground = color.Black
var colorOutline = color.Black
var colorCalendarWeekdayHeader = color.RGBA { R: 194, G: 162, B: 132, A: 255 }
var colorCalendarWeekend = color.RGBA { R: 165, G: 185, B: 120, A: 255 }
var colorCalendarDay = color.RGBA { R: 194, G: 189, B: 132, A: 255 }
var colorInactive = color.RGBA { R: 131, G: 147, B: 134, A: 255 }
var outline = tomo.Border {
Width: tomo.I(1),
Color: [4]color.Color {
colorOutline,
colorOutline,
colorOutline,
colorOutline,
},
}
var borderColorOutline = [4]color.Color { colorOutline, colorOutline, colorOutline, colorOutline }
var borderColorEngraved = [4]color.Color { colorShadow, colorHighlight, colorHighlight, colorShadow }
var borderColorLifted = [4]color.Color { colorHighlight, colorShadow, colorShadow, colorHighlight }
var borderColorInput = [4]color.Color { colorInputShadow, colorInput, colorInput, colorInputShadow }
var borderColorFocused = [4]color.Color { colorFocus, colorFocus, colorFocus, colorFocus }
var borderColorShade = [4]color.Color { colorShade, colorShade, colorShade, colorShade }
var rules = []style.Rule {
// *.*[*]
style.Rule {
Default: style.AS (
style.AttrFace { Face: basicfont.Face7x13 },
style.AttrTextColor { Color: tomo.ColorForeground },
style.AttrDotColor { Color: tomo.ColorAccent },
style.AttrGap { X: 8, Y: 8 },
),
},
// *.Button[*]
style.Rule {
Role: tomo.R("", "Button", ""),
Default: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorLifted,
},
},
style.AttrPadding(tomo.I(4, 8)),
style.AttrColor { Color: tomo.ColorRaised },
),
Pressed: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1, 0, 0, 1),
Color: borderColorEngraved,
},
},
style.AttrPadding(tomo.I(5, 8, 4, 9)),
style.AttrColor { Color: colorCarvedPressed },
),
Focused: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorFocused,
},
},
style.AttrPadding(tomo.I(4, 8)),
),
},
// *.TextInput[*]
style.Rule {
Role: tomo.R("", "TextInput", ""),
Default: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorInput,
},
},
style.AttrColor { Color: colorInput },
style.AttrPadding(tomo.I(5, 4, 4, 5)),
),
Focused: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorFocused,
},
},
),
},
// *.TextView[*]
style.Rule {
Role: tomo.R("", "TextView", ""),
Default: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1, 0, 0, 1),
Color: borderColorEngraved,
},
},
style.AttrColor { Color: tomo.ColorSunken },
style.AttrPadding(tomo.I(8)),
),
},
// *.NumberInput[*]
style.Rule {
Role: tomo.R("", "NumberInput", ""),
Default: style.AS (
style.AttrGap { },
),
},
// *.Container[sunken]
style.Rule {
Role: tomo.R("", "Container", "sunken"),
Default: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1, 0, 0, 1),
Color: borderColorEngraved,
},
},
style.AttrColor { Color: tomo.ColorSunken },
style.AttrPadding(tomo.I(8)),
),
},
// *.Container[outer]
style.Rule {
Role: tomo.R("", "Container", "outer"),
Default: style.AS (
style.AttrColor { Color: tomo.ColorBackground },
style.AttrPadding(tomo.I(8)),
),
},
// *.Container[menu]
style.Rule {
Role: tomo.R("", "Container", "menu"),
Default: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorLifted,
},
},
style.AttrColor { Color: tomo.ColorBackground },
style.AttrGap { },
),
},
// *.Heading[*]
style.Rule {
Role: tomo.R("", "Heading", ""),
Default: style.AS (
style.AttrAlign { X: tomo.AlignMiddle, Y: tomo.AlignMiddle },
),
},
// *.Separator[*]
style.Rule {
Role: tomo.R("", "Separator", ""),
Default: style.AS (
style.AttrBorder {
tomo.Border {
Width: tomo.I(1),
Color: borderColorEngraved,
},
},
),
},
// *.Slider[*]
style.Rule {
Role: tomo.R("", "Slider", ""),
Default: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1, 0, 0, 1),
Color: borderColorEngraved,
},
},
style.AttrColor { Color: colorGutter },
style.AttrPadding(tomo.I(0, 1, 1, 0)),
),
Focused: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorFocused,
},
},
style.AttrPadding(tomo.I(0)),
),
},
// *.Slider[horizontal]
style.Rule {
Role: tomo.R("", "Slider", "horizontal"),
Default: style.AS(style.AttrMinimumSize { X: 48 }),
},
// *.Slider[vertical]
style.Rule {
Role: tomo.R("", "Slider", "vertical"),
Default: style.AS(style.AttrMinimumSize { Y: 48 }),
},
// *.SliderHandle[*]
style.Rule {
Role: tomo.R("", "SliderHandle", ""),
Default: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorLifted,
},
},
style.AttrColor { Color: tomo.ColorRaised },
style.AttrMinimumSize { X: 12, Y: 12, },
),
},
// *.Checkbox[*]
style.Rule {
Role: tomo.R("", "Checkbox", ""),
Default: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1, 0, 0, 1),
Color: borderColorEngraved,
},
},
style.AttrColor { Color: tomo.ColorSunken },
style.AttrPadding(tomo.I(0, 1, 1, 0)),
style.AttrMinimumSize { X: 19, Y: 19 },
),
Focused: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorFocused,
},
},
style.AttrPadding(tomo.I(0)),
),
},
// *.MenuItem[*]
style.Rule {
Role: tomo.R("", "MenuItem", ""),
Default: style.AS (
style.AttrPadding(tomo.I(4)),
style.AttrGap { X: 4, Y: 4 },
style.AttrColor { Color: color.Transparent },
),
Hovered: style.AS (
style.AttrColor { Color: tomo.ColorAccent },
),
Focused: style.AS (
style.AttrColor { Color: tomo.ColorAccent },
),
},
// *.File[*]
style.Rule {
Role: tomo.R("", "File", ""),
Default: style.AS (
style.AttrColor { Color: color.Transparent },
),
Focused: style.AS (
style.AttrColor { Color: tomo.ColorAccent },
),
},
// *.TearLine[*]
style.Rule {
Role: tomo.R("", "TearLine", ""),
Default: style.AS (
style.AttrBorder {
tomo.Border {
Width: tomo.I(3),
Color: [4]color.Color {
color.Transparent,
color.Transparent,
color.Transparent,
color.Transparent,
},
},
},
style.AttrColor { Color: tomo.ColorForeground },
style.AttrPadding(tomo.I(1, 0, 0, 1)),
),
Hovered: style.AS (
style.AttrBorder {
tomo.Border {
Width: tomo.I(3),
Color: borderColorFocused,
},
},
),
Focused: style.AS (
style.AttrBorder {
tomo.Border {
Width: tomo.I(3),
Color: borderColorFocused,
},
},
),
},
// *.Calendar[*]
style.Rule {
Role: tomo.R("", "Calendar", ""),
Default: style.AS (
style.AttrBorder {
tomo.Border {
Width: tomo.I(0, 1, 1, 0),
Color: borderColorShade,
},
outline,
},
style.AttrColor { Color: colorInput },
style.AttrPadding(tomo.I(2)),
style.AttrGap { X: 2, Y: 2 },
),
},
// *.CalendarGrid[*]
style.Rule {
Role: tomo.R("", "CalendarGrid", ""),
Default: style.AS (
style.AttrGap { X: 2, Y: 2 },
),
},
// *.CalendarWeekdayHeader[*]
style.Rule {
Role: tomo.R("", "CalendarWeekdayHeader", ""),
Default: style.AS (
style.AttrPadding(tomo.I(2)),
style.AttrColor { Color: colorCalendarWeekdayHeader },
),
},
// *.CalendarDay[weekday]
style.Rule {
Role: tomo.R("", "CalendarDay", "weekday"),
Default: style.AS (
style.AttrPadding(tomo.I(2)),
style.AttrMinimumSize { X: 32, Y: 32 },
style.AttrColor { Color: colorCalendarDay },
),
},
// *.CalendarDay[weekend]
style.Rule {
Role: tomo.R("", "CalendarDay", "weekend"),
Default: style.AS (
style.AttrPadding(tomo.I(2)),
style.AttrMinimumSize { X: 32, Y: 32 },
style.AttrColor { Color: colorCalendarWeekend },
),
},
// *.TabbedContainer[*]
style.Rule {
Role: tomo.R("", "TabbedContainer", ""),
Default: style.AS (
style.AttrGap { },
),
},
// *.TabRow[*]
style.Rule {
Role: tomo.R("", "TabRow", ""),
Default: style.AS (
style.AttrBorder {
tomo.Border {
Width: tomo.I(1, 1, 0, 1),
Color: borderColorOutline,
},
tomo.Border {
Width: tomo.I(1, 0, 0, 1),
Color: borderColorEngraved,
},
},
style.AttrGap { X: 0, Y: 0 },
style.AttrColor { Color: colorGutter },
style.AttrPadding(tomo.I(1, 0, 0, 0)),
),
},
// *.TabSpacer[left]
style.Rule {
Role: tomo.R("", "TabSpacer", "left"),
Default: style.AS (
style.AttrBorder {
tomo.Border {
Width: tomo.I(0, 0, 1, 0),
Color: borderColorEngraved,
},
tomo.Border {
Width: tomo.I(0, 0, 1, 0),
Color: borderColorOutline,
},
},
style.AttrMinimumSize { X: 1 },
),
},
// *.TabSpacer[right]
style.Rule {
Role: tomo.R("", "TabSpacer", "right"),
Default: style.AS (
style.AttrBorder {
tomo.Border {
Width: tomo.I(1, 0, 0, 0),
Color: [4]color.Color {
colorGutter, colorGutter,
colorGutter, colorGutter,
},
},
tomo.Border {
Width: tomo.I(0, 0, 1, 0),
Color: borderColorEngraved,
},
tomo.Border {
Width: tomo.I(0, 0, 1, 1),
Color: borderColorOutline,
},
tomo.Border {
Width: tomo.I(0, 0, 0, 1),
Color: borderColorShade,
},
},
style.AttrMinimumSize { X: 3 },
),
},
// *.Tab[*]
style.Rule {
Role: tomo.R("", "Tab", ""),
Default: style.AS (
style.AttrBorder {
tomo.Border {
Width: tomo.I(1, 0, 0, 0),
Color: [4]color.Color {
colorGutter, colorGutter,
colorGutter, colorGutter,
},
},
tomo.Border {
Width: tomo.I(0, 0, 1, 0),
Color: borderColorEngraved,
},
tomo.Border {
Width: tomo.I(1, 0, 1, 1),
Color: borderColorOutline,
},
tomo.Border {
Width: tomo.I(1, 1, 0, 1),
Color: borderColorLifted,
},
},
style.AttrPadding(tomo.I(4, 8, 4, 8)),
style.AttrColor { Color: tomo.ColorRaised },
),
},
// *.Tab[active]
style.Rule {
Role: tomo.R("", "Tab", "active"),
Default: style.AS (
style.AttrBorder {
tomo.Border {
Width: tomo.I(1, 0, 0, 1),
Color: borderColorOutline,
},
tomo.Border {
Width: tomo.I(1, 1, 0, 1),
Color: borderColorLifted,
},
},
style.AttrPadding(tomo.I(4, 8, 4, 8)),
style.AttrColor { Color: tomo.ColorBackground },
),
},
// *.Swatch[*]
style.Rule {
Role: tomo.R("", "Swatch", ""),
Default: style.AS (
style.AttrBorder {
outline,
},
style.AttrMinimumSize { X: 19, Y: 19 },
),
Focused: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorFocused,
},
},
),
},
// *.ColorPickerMap[*]
style.Rule {
Role: tomo.R("", "ColorPickerMap", ""),
Default: style.AS (
style.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1, 0, 0, 1),
Color: borderColorEngraved,
},
},
style.AttrColor { Color: tomo.ColorSunken },
style.AttrMinimumSize { X: 128, Y: 128 },
),
},
}

View File

@ -1,20 +0,0 @@
package fallbackStyle
import "image/color"
import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/nasin/internal/style"
// New returns Wintergreen, the default Tomo style. It is neutral-gray with
// green and turquoise accents.
func New () *style.Style {
return &style.Style {
Colors: map[tomo.Color] color.Color {
tomo.ColorBackground: colorBackground,
tomo.ColorForeground: colorForeground,
tomo.ColorRaised: colorCarved,
tomo.ColorSunken: colorCarved,
tomo.ColorAccent: colorFocus,
},
Rules: rules,
}
}

View File

@ -1,4 +1,4 @@
package style
package theme
import "image"
import "image/color"

View File

@ -0,0 +1,231 @@
package defaultTheme
import "image/color"
import "git.tebibyte.media/tomo/tomo"
import "golang.org/x/image/font/basicfont"
import "git.tebibyte.media/tomo/tomo/theme"
import dataTheme "git.tebibyte.media/tomo/nasin/internal/theme"
var colorFocus = color.RGBA { R: 61, G: 128, B: 143, A: 255 }
var colorInput = color.RGBA { R: 208, G: 203, B: 150, A: 255 }
var colorCarved = color.RGBA { R: 151, G: 160, B: 150, A: 255 }
var colorShadow = color.RGBA { R: 57, G: 59, B: 57, A: 255 }
var colorInputShadow = color.RGBA { R: 143, G: 146, B: 91, A: 255 }
var colorHighlight = color.RGBA { R: 207, G: 215, B: 210, A: 255 }
var colorBackground = color.RGBA { R: 169, G: 171, B: 168, A: 255 }
var colorCarvedPressed = color.RGBA { R: 129, G: 142, B: 137, A: 255 }
var colorForeground = color.Black
var colorOutline = color.Black
var outline = tomo.Border {
Width: tomo.I(1),
Color: [4]color.Color {
colorOutline,
colorOutline,
colorOutline,
colorOutline,
},
}
var borderColorEngraved = [4]color.Color { colorShadow, colorHighlight, colorHighlight, colorShadow }
var borderColorLifted = [4]color.Color { colorHighlight, colorShadow, colorShadow, colorHighlight }
var borderColorInput = [4]color.Color { colorInputShadow, colorInput, colorInput, colorInputShadow }
var borderColorFocused = [4]color.Color { colorFocus, colorFocus, colorFocus, colorFocus }
var rules = []dataTheme.Rule {
// *.*[*]
dataTheme.Rule {
Default: dataTheme.AS (
dataTheme.AttrFace { Face: basicfont.Face7x13 },
dataTheme.AttrTextColor { Color: theme.ColorForeground },
dataTheme.AttrDotColor { Color: theme.ColorAccent },
dataTheme.AttrGap { X: 8, Y: 8 },
),
},
// *.Button[*]
dataTheme.Rule {
Role: theme.R("", "Button", ""),
Default: dataTheme.AS (
dataTheme.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorLifted,
},
},
dataTheme.AttrPadding(tomo.I(4, 8)),
dataTheme.AttrColor { Color: theme.ColorRaised },
),
Pressed: dataTheme.AS (
dataTheme.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1, 0, 0, 1),
Color: borderColorEngraved,
},
},
dataTheme.AttrPadding(tomo.I(5, 8, 4, 9)),
dataTheme.AttrColor { Color: colorCarvedPressed },
),
Focused: dataTheme.AS (
dataTheme.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorFocused,
},
},
dataTheme.AttrPadding(tomo.I(4, 8)),
),
},
// *.TextInput[*]
dataTheme.Rule {
Role: theme.R("", "TextInput", ""),
Default: dataTheme.AS (
dataTheme.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorInput,
},
},
dataTheme.AttrColor { Color: colorInput },
dataTheme.AttrPadding(tomo.I(5, 4, 4, 5)),
),
Focused: dataTheme.AS (
dataTheme.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorFocused,
},
},
),
},
// *.NumberInput[*]
dataTheme.Rule {
Role: theme.R("", "NumberInput", ""),
Default: dataTheme.AS (
dataTheme.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorInput,
},
},
dataTheme.AttrColor { Color: colorInput },
dataTheme.AttrPadding(tomo.I(5, 4, 4, 5)),
),
Focused: dataTheme.AS (
dataTheme.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorFocused,
},
},
),
},
// *.Container[outer]
dataTheme.Rule {
Role: theme.R("", "Container", "outer"),
Default: dataTheme.AS (
dataTheme.AttrColor { Color: theme.ColorBackground },
dataTheme.AttrPadding(tomo.I(8)),
),
},
// *.Heading[*]
dataTheme.Rule {
Role: theme.R("", "Heading", ""),
Default: dataTheme.AS (
dataTheme.AttrAlign { X: tomo.AlignMiddle, Y: tomo.AlignMiddle },
),
},
// *.Separator[*]
dataTheme.Rule {
Role: theme.R("", "Separator", ""),
Default: dataTheme.AS (
dataTheme.AttrBorder {
tomo.Border {
Width: tomo.I(1),
Color: borderColorEngraved,
},
},
),
},
// *.Slider[*]
dataTheme.Rule {
Role: theme.R("", "Slider", ""),
Default: dataTheme.AS (
dataTheme.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1, 0, 0, 1),
Color: borderColorEngraved,
},
},
dataTheme.AttrColor { Color: theme.ColorSunken },
dataTheme.AttrPadding(tomo.I(0, 1, 1, 0)),
),
Focused: dataTheme.AS (
dataTheme.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorFocused,
},
},
dataTheme.AttrPadding(tomo.I(0)),
),
},
// *.Slider[horizontal]
dataTheme.Rule {
Role: theme.R("", "Slider", "horizontal"),
Default: dataTheme.AS(dataTheme.AttrMinimumSize { X: 48 }),
},
// *.Slider[vertical]
dataTheme.Rule {
Role: theme.R("", "Slider", "vertical"),
Default: dataTheme.AS(dataTheme.AttrMinimumSize { Y: 48 }),
},
// *.SliderHandle[*]
dataTheme.Rule {
Role: theme.R("", "SliderHandle", ""),
Default: dataTheme.AS (
dataTheme.AttrBorder {
outline,
tomo.Border {
Width: tomo.I(1),
Color: borderColorLifted,
},
},
dataTheme.AttrColor { Color: theme.ColorRaised },
dataTheme.AttrMinimumSize { X: 12, Y: 12, },
),
},
}
// Theme returns Wintergreen, the default Tomo theme. It is neutral-gray with
// green and turquoise accents.
func Theme () theme.Theme {
return &dataTheme.Theme {
Colors: map[theme.Color] color.Color {
theme.ColorBackground: colorBackground,
theme.ColorForeground: colorForeground,
theme.ColorRaised: colorCarved,
theme.ColorSunken: colorCarved,
theme.ColorAccent: colorFocus,
},
Rules: rules,
}
}

View File

@ -1,4 +1,4 @@
package style
package theme
import "image"
import "image/color"

View File

@ -1,17 +1,19 @@
// Package style provides a data-driven style implementation.
package style
// Package theme provides a data-driven theme implementation.
package theme
import "image"
import "image/color"
import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/event"
import "git.tebibyte.media/tomo/tomo/input"
import "git.tebibyte.media/tomo/tomo/theme"
import "git.tebibyte.media/tomo/tomo/canvas"
// this is CSS's bastard child
// Style allows the use of data to define a visual style.
type Style struct {
// Theme allows the use of data to define a visual style.
type Theme struct {
// Textures maps texture names to image textures.
Textures map[string] image.Image
textures map[string] canvas.TextureCloser // private texture cache
@ -20,14 +22,26 @@ type Style struct {
// Rules determines which styles get applied to which Objects.
Rules []Rule
// Colors maps tomo.Color values to color.RGBA values.
Colors map[tomo.Color] color.Color
// Colors maps theme.Color values to color.RGBA values.
Colors map[theme.Color] color.Color
// This type does not handle icons, and as such, a special icon theme
// must be separately specified.
IconTheme
}
// IconTheme implements the part of theme.Theme that handles icons.
type IconTheme interface {
// Icon returns a texture of the corresponding icon ID.
Icon (theme.Icon, theme.IconSize) canvas.Texture
// MimeIcon returns an icon corresponding to a MIME type.
MimeIcon (data.Mime, theme.IconSize) canvas.Texture
}
// Rule describes under what circumstances should certain style attributes be
// active.
type Rule struct {
Role tomo.Role
Role theme.Role
Default AttrSet
Hovered AttrSet
Pressed AttrSet
@ -83,7 +97,7 @@ func (this *AttrSet) ensure () {
if this.set == nil { this.set = make(map[int] Attr) }
}
func (this *Style) execute (object tomo.Object, set AttrSet) {
func (this *Theme) execute (object tomo.Object, set AttrSet) {
box := object.GetBox()
for _, attr := range set.set {
@ -91,7 +105,7 @@ func (this *Style) execute (object tomo.Object, set AttrSet) {
case AttrColor:
box.SetColor(attr.Color)
case AttrTexture:
box.SetTextureTile(this.texture(string(attr)))
box.SetTexture(this.texture(string(attr)))
case AttrBorder:
box.SetBorder([]tomo.Border(attr)...)
case AttrMinimumSize:
@ -119,12 +133,12 @@ func (this *Style) execute (object tomo.Object, set AttrSet) {
box.SetAlign(attr.X, attr.Y)
}
default:
panic("bug: nasin/internal/tomo.Theme: unexpected attribute")
panic("bug: nasin/internal/theme.Theme: unexpected attribute")
}
}
}
func (this *Style) texture (name string) canvas.Texture {
func (this *Theme) texture (name string) canvas.Texture {
this.ensureTextureCache()
if texture, ok := this.textures[name]; ok {
return texture
@ -139,19 +153,19 @@ func (this *Style) texture (name string) canvas.Texture {
return this.missingTexture()
}
func (this *Style) missingTexture () canvas.Texture {
func (this *Theme) missingTexture () canvas.Texture {
if this.missing == nil {
this.missing = tomo.NewTexture(missingTexture(16))
}
return this.missing
}
func (this *Style) ensureTextureCache () {
func (this *Theme) ensureTextureCache () {
if this.textures == nil { this.textures = make(map[string] canvas.TextureCloser) }
}
// setsFor builds flattened attr sets for a specific role based on the rules list
func (this *Style) setsFor (role tomo.Role) (defaul, hovered, pressed, focused AttrSet) {
func (this *Theme) setsFor (role theme.Role) (defaul, hovered, pressed, focused AttrSet) {
for _, current := range this.Rules {
// check for a match
packageMatch := current.Role.Package == role.Package || current.Role.Package == ""
@ -173,11 +187,10 @@ func (this *Style) setsFor (role tomo.Role) (defaul, hovered, pressed, focused A
return defaul, hovered, pressed, focused
}
func (this *Style) Apply (object tomo.Object) event.Cookie {
func (this *Theme) Apply (object tomo.Object, role theme.Role) event.Cookie {
pressed := false
hovered := false
box := object.GetBox()
role := box.Role()
defaultSet, hoveredSet, pressedSet, focusedSet := this.setsFor(role)
@ -219,16 +232,32 @@ func (this *Style) Apply (object tomo.Object) event.Cookie {
}
func (this *Style) RGBA (c tomo.Color) (r, g, b, a uint32) {
func (this *Theme) RGBA (c theme.Color) (r, g, b, a uint32) {
if this.Colors == nil { return 0xFFFF, 0, 0xFFFF, 0xFFFF }
color, ok := this.Colors[c]
if !ok { return 0xFFFF, 0, 0xFFFF, 0xFFFF }
return color.RGBA()
}
// Close closes all cached textures this style has open. Do not call this while
// the style is in use.
func (this *Style) Close () error {
func (this *Theme) Icon (icon theme.Icon, size theme.IconSize) canvas.Texture {
if this.IconTheme == nil {
return this.missingTexture()
} else {
return this.IconTheme.Icon(icon, size)
}
}
func (this *Theme) MimeIcon (mime data.Mime, size theme.IconSize) canvas.Texture {
if this.IconTheme == nil {
return this.missingTexture()
} else {
return this.IconTheme.MimeIcon(mime, size)
}
}
// Close closes all cached textures this theme has open. Do not call this while
// the theme is in use.
func (this *Theme) Close () error {
this.missing.Close()
this.missing = nil
for _, texture := range this.textures {

198
path.go
View File

@ -1,32 +1,190 @@
package nasin
import "io"
import "os"
import "io/fs"
import "strings"
import "path/filepath"
// ApplicationUserDataDir returns the directory path where an application can
// store its user data files. If the directory does not exist, it will be
// created.
func ApplicationUserDataDir (app ApplicationDescription) (string, error) {
return userMkdirAll(app.ID, userDataDir)
// FS is Tomo's implementation of fs.FS. It provides access to a specific part
// of the filesystem.
type FS struct {
path string
}
// ApplicationUserConfigDir returns the directory path where an application can
// store its user configuration files.
func ApplicationUserConfigDir (app ApplicationDescription) (string, error) {
return userMkdirAll(app.ID, userConfigDir)
// FileWriter is a writable version of fs.File.
type FileWriter interface {
fs.File
io.Writer
}
// ApplicationUserCacheDir returns the directory path where an application can
// store its user cache files.
func ApplicationUserCacheDir (app ApplicationDescription) (string, error) {
return userMkdirAll(app.ID, userCacheDir)
// ApplicationUserDataFS returns an FS that an application can use to store user
// data files.
func ApplicationUserDataFS (app ApplicationDescription) (*FS, error) {
dataDir, err := userDataDir()
if err != nil { return nil, err }
return appFs(dataDir, app)
}
func userMkdirAll (sub string, getter func () (string, error)) (string, error) {
path, err := getter()
if err != nil { return "", err }
path = filepath.Join(path, sub)
err = os.MkdirAll(path, 0700)
if err != nil { return "", err }
return path, nil
// ApplicationUserConfigFS returns an FS that an application can use to store
// user configuration files.
func ApplicationUserConfigFS (app ApplicationDescription) (*FS, error) {
configDir, err := userConfigDir()
if err != nil { return nil, err }
return appFs(configDir, app)
}
// ApplicationUserCacheFS returns an FS that an application can use to store
// user cache files.
func ApplicationUserCacheFS (app ApplicationDescription) (*FS, error) {
cacheDir, err := userCacheDir()
if err != nil { return nil, err }
return appFs(cacheDir, app)
}
func pathErr (op, path string, err error) error {
return &fs.PathError {
Op: op,
Path: path,
Err: err,
}
}
func appFs (root string, app ApplicationDescription) (*FS, error) {
// remove slashes
appid := app.ID
appid = strings.ReplaceAll(appid, "/", "-")
appid = strings.ReplaceAll(appid, "\\", "-")
path := filepath.Join(root, appid)
// ensure the directory actually exists
err := os.MkdirAll(path, 700)
if err != nil { return nil, err }
return &FS { path: path }, nil
}
func (this FS) subPath (name string) (string, error) {
if !fs.ValidPath(name) { return "", fs.ErrInvalid }
if strings.Contains(name, "/") { return "", fs.ErrInvalid }
return filepath.Join(this.path, name), nil
}
// Open opens the named file.
func (this FS) Open (name string) (fs.File, error) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("open", name, err)
}
return os.Open(path)
}
// Create creates or truncates the named file.
func (this FS) Create (name string) (FileWriter, error) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("create", name, err)
}
return os.Create(path)
}
// OpenFile is the generalized open call; most users will use Open or Create
// instead.
func (this FS) OpenFile (
name string,
flag int,
perm os.FileMode,
) (
FileWriter,
error,
) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("open", name, err)
}
return os.OpenFile(path, flag, perm)
}
// ReadDir reads the named directory and returns a list of directory entries
// sorted by filename.
func (this FS) ReadDir (name string) ([]fs.DirEntry, error) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("readdir", name, err)
}
return os.ReadDir(path)
}
// ReadFile reads the named file and returns its contents.
// A successful call returns a nil error, not io.EOF.
// (Because ReadFile reads the whole file, the expected EOF
// from the final Read is not treated as an error to be reported.)
//
// The caller is permitted to modify the returned byte slice.
func (this FS) ReadFile (name string) ([]byte, error) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("readfile", name, err)
}
return os.ReadFile(path)
}
// WriteFile writes data to the named file, creating it if necessary.
func (this FS) WriteFile (name string, data []byte, perm os.FileMode) error {
path, err := this.subPath(name)
if err != nil {
return pathErr("writefile", name, err)
}
return os.WriteFile(path, data, perm)
}
// Stat returns a FileInfo describing the file.
func (this FS) Stat (name string) (fs.FileInfo, error) {
path, err := this.subPath(name)
if err != nil {
return nil, pathErr("stat", name, err)
}
return os.Stat(path)
}
// Remove removes the named file or (empty) directory.
func (this FS) Remove (name string) error {
path, err := this.subPath(name)
if err != nil {
return pathErr("remove", name, err)
}
return os.Remove(path)
}
// RemoveAll removes name and any children it contains.
func (this FS) RemoveAll (name string) error {
path, err := this.subPath(name)
if err != nil {
return pathErr("removeall", name, err)
}
return os.RemoveAll(path)
}
// Rename renames (moves) oldname to newname.
func (this FS) Rename (oldname, newname string) error {
oldpath, err := this.subPath(oldname)
if err != nil {
return pathErr("rename", oldname, err)
}
newpath, err := this.subPath(newname)
if err != nil {
return pathErr("rename", newname, err)
}
return os.Rename(oldpath, newpath)
}