Help us shape the future of Go! Take the 2026 Go Developer Survey

ui

package
v0.0.0-...-ea410ed Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2026 License: BSD-3-Clause Imports: 28 Imported by: 0

Documentation

Overview

Package ui renders the aggregator — a topbar with search, a sidebar of source subscriptions, and a unified newest-first feed of items from every source — into an RGBA pixel buffer. Chrome and cards are drawn with the go-widgets painter; text is anti-aliased TrueType (see text.go) so it stays clean at any zoom / Retina scale. The package has no build tag, so its layout, hit-testing and rendering run under native `go test` and are snapshot-verifiable.

Index

Constants

View Source
const (
	MinW     = 360
	MinH     = 240
	MinZoom  = 0.5
	MaxZoom  = 3.0
	ZoomStep = 0.1
)

Minimum sensible surface and zoom bounds.

View Source
const (
	OSMac     = "mac"
	OSLinux   = "linux"
	OSWindows = "windows"
)

OS tokens understood by ThemeFor. The front-end derives these from navigator.platform / userAgentData.

View Source
const AllFilter = -1

AllFilter is the Active value that shows every source (no filter).

Variables

This section is empty.

Functions

func MenuIconFolder(p painter.Painter, cell toolkit.Rect, ink toolkit.RGBA)
func MenuIconMarkRead(p painter.Painter, cell toolkit.Rect, ink toolkit.RGBA)
func MenuIconNewFolder(p painter.Painter, cell toolkit.Rect, ink toolkit.RGBA)

MenuIconNewFolder and MenuIconFolder paint the sidebar folder context-menu glyphs into the cell the toolkit Menu hands them, in the row's own ink (so they invert on hover). They match toolkit.MenuItem.Icon so the Handler can hand them straight to the menu without this package's private icon handles leaking out.

func MenuIconRefresh(p painter.Painter, cell toolkit.Rect, ink toolkit.RGBA)

MenuIconRefresh, MenuIconMarkRead and MenuIconUnsubscribe paint the sidebar context menu's row glyphs into the cell the toolkit Menu hands them, in the row's own ink (so they invert on hover). They match toolkit.MenuItem.Icon, so the Handler can hand them straight to the menu without this package's private icon handles leaking out.

func MenuIconUnsubscribe(p painter.Painter, cell toolkit.Rect, ink toolkit.RGBA)

func ResolveTheme

func ResolveTheme(name, os string, prefersDark bool) *toolkit.Theme

ResolveTheme maps a persisted theme name ("system"|"light"|"dark") plus the host OS and the system dark preference to a concrete palette.

func SetSystemFont

func SetSystemFont(ttf []byte) bool

SetSystemFont installs a host UI typeface (e.g. macOS SF from /System/Library/Fonts/SFNS.ttf) so the reader matches the native look. SF is shipped as a variable font whose only instance the pure-Go rasteriser can reach is Regular, so bold weights are synthesised by over-striking (see toolkit.NewSyntheticBoldFont). It returns false and keeps the embedded Go fonts if ttf cannot be parsed. The font cache is dropped so already-built chains re-derive from the new primary.

func ThemeFor

func ThemeFor(os string, dark bool) *toolkit.Theme

ThemeFor returns the palette matching os (one of the OS* tokens) in the requested light/dark variant. Unknown platforms fall back to the toolkit default theme.

func WithAccent

func WithAccent(t *toolkit.Theme, r, g, b uint8) *toolkit.Theme

WithAccent overrides a theme's accent colour with a harvested system accent (e.g. macOS -[NSColor controlAccentColor]) and recomputes the on-accent label colour so topbar text stays legible whatever hue the user picked.

Types

type A11yNode

type A11yNode struct {
	toolkit.A11yInfo
	Rect toolkit.Rect
}

A11yNode is one element of the reader's accessibility tree: the toolkit's description of it, plus where it sits on screen so a platform bridge can hit-test and focus it.

Rect is in screen coordinates. A feed card scrolled out of view still appears in the tree, with a rect above or below the viewport: a reader must be able to walk the whole list, not only the part that happens to be painted, and it is the bridge's job to scroll to what the user selects.

type AuthPrompt

type AuthPrompt struct {
	Kind   source.Kind
	Reason string
}

AuthPrompt names a provider whose subscription failed because it needs the user to sign in or supply configuration. The feed renders one clickable "needs sign-in" banner per prompt; a click opens the Accounts editor for Kind.

type DownloadItem

type DownloadItem struct {
	ID     string
	Name   string
	Status DownloadStatus
	Done   int
	Total  int
}

DownloadItem is one row of the download panel, keyed by the post's release base (the same id the group card's checkbox toggles).

type DownloadStatus

type DownloadStatus int

DownloadStatus is the lifecycle state of a queued download.

const (
	DLQueued DownloadStatus = iota // waiting for a worker
	DLActive                       // downloading (Done/Total parts)
	DLDone                         // reconstructed + saved
	DLFailed                       // errored
)

type Focus

type Focus int

Focus names which settings text field currently has keyboard focus.

const (
	FocusNone Focus = iota
	FocusChannel
	FocusRename
	FocusCache
	FocusCacheSize    // the media-cache size-limit (MB) field
	FocusCacheBackend // the media-cache backend (plugin path) field
	FocusZoomIn       // the "zoom in" browser-shortcut key field
	FocusZoomOut      // the "zoom out" browser-shortcut key field
)

Settings-view focus states.

type Hit

type Hit struct {
	Kind    HitKind
	Item    source.Item // HitItem
	Sub     int         // HitSub: index into Subs, or AllFilter; HitRemoveSub: sub index
	Profile int         // HitProfile / HitSelectProfile / HitDeleteProfile / HitRemoveSub / HitRenameProfile
	Value   string      // HitTheme / HitSelectKind / HitImportFollows (source kind)
}

Hit is the result of Scene.HitTest.

type HitKind

type HitKind int

HitKind classifies what a click landed on.

const (
	HitNone               HitKind = iota
	HitItem                       // a feed item (open it in the detail view) — Item set
	HitSub                        // a sidebar subscription (filter the feed) — Sub set (AllFilter = All)
	HitSearch                     // the topbar search field (focus it)
	HitBack                       // the detail view's back button (return to the feed)
	HitOpenExternal               // the detail view's "open original" button — Item set
	HitProfile                    // a sidebar profile tab (switch active profile) — Profile set
	HitSettings                   // the sidebar ⚙ Settings entry (open preferences)
	HitLog                        // the sidebar Network-log entry (open the HTTP log)
	HitCloseLog                   // the log view's "< Back" button (return to the feed)
	HitBurger                     // the topbar burger button (collapse/expand the sidebar)
	HitRefresh                    // the topbar refresh button (re-fetch the feed) — mirrors the pull-to-refresh gesture
	HitSidebarDivider             // the draggable divider at the sidebar's right edge
	HitFixAuth                    // an in-feed "needs sign-in" banner row — Value = source kind
	HitToggleGroup                // a Usenet group card's header/chevron — Value = release base
	HitReconstruct                // a Usenet group card's "Reconstruct" affordance — Value = release base
	HitPreviewGroup               // a Usenet group card's body (preview it in the pane) — Value = release base
	HitOpenPreview                // the preview pane's "Open" button (full reading view) — Item set
	HitPreviewDivider             // the preview pane's left-edge resize grip (start a drag)
	HitPreviewTextSmaller         // the preview toolbar's "A−" pill (shrink the reader text)
	HitPreviewTextLarger          // the preview toolbar's "A+" pill (grow the reader text)
	HitToggleDownload             // a complete post's download checkbox — Value = release base
	HitClearDownloads             // the download panel's "Clear" button
	HitToggleFolder               // a sidebar virtual-folder row (collapse/expand) — Value = folder name
	HitToggleSource               // a sidebar source-group header (accordion expand/collapse) — Value = source kind

	// Newsgroup browser (Mode == ModeBrowse):
	HitBrowse           // the sidebar "+ Browse newsgroups" entry (open the browser)
	HitCloseBrowse      // the browser's "‹ Back" button (return to the feed)
	HitBrowseRefresh    // the browser's Refresh button (re-fetch the full group list)
	HitBrowseFilter     // the browser's regexp filter field (focus it)
	HitToggleBrowseNode // a tree node's chevron/row (expand/collapse) — Value = node name
	HitSubscribeGroup   // a tree leaf's Subscribe affordance — Value = full group name
	HitUnsubscribeGroup // a subscribed tree leaf's ✓ (unsubscribe) — Value = full group name

	// Settings-view actions (Mode == ModeSettings):
	HitSelectProfile // Profile = index being edited
	HitNewProfile
	HitDeleteProfile          // Profile = index
	HitRenameProfile          // focus the rename field for Profile = index
	HitSelectKind             // Value = source kind for the add-subscription palette
	HitAddSub                 // commit the channel input into the edited profile
	HitRemoveSub              // Profile = index, Sub = subscription index
	HitFocusChannel           // focus the add-channel input
	HitFocusCache             // focus the media-cache path input
	HitFocusCacheSize         // focus the media-cache size-limit (MB) input
	HitFocusCacheBackend      // focus the media-cache backend (plugin path) input
	HitFocusZoomIn            // focus the zoom-in browser-shortcut key input
	HitFocusZoomOut           // focus the zoom-out browser-shortcut key input
	HitTheme                  // Value = "system"|"light"|"dark"
	HitSignInBrowser          // Value = "default"|"firefox"|"chrome"|"safari"|"edge" (sign-in browser)
	HitBrowserTabs            // Value = "multi"|"single" (web-preview browser tab mode)
	HitBrowserChrome          // Value = "shown"|"hidden" (web-preview toolbar/urlbar visibility)
	HitInfiniteScroll         // Value = "on"|"off" (fetch next page on scroll-to-bottom)
	HitDismissPreviewOnSwitch // Value = "on"|"off" (close the preview when the sub/group changes)
	HitBiometricUnlock        // Value = "on"|"off" (store/read secrets behind a biometric gate)
	HitClearRenderCache       // empty the web-preview render cache
	HitCloseSettings          // leave the settings view

	// Accounts-view actions (Mode == ModeAccounts):
	HitAccounts            // the sidebar 👤 Accounts entry (open the accounts editor)
	HitCloseAccounts       // leave the accounts view (commit)
	HitSelectAccount       // Value = source kind for the provider being edited
	HitFocusAccountField   // Value = credential field key to focus
	HitToggleAccountBool   // Value = bool credential field key to flip (Usenet TLS)
	HitImportRedditFirefox // the Reddit editor's "Import session from Firefox" button
	HitImportFollows       // a follow-capable provider's "Import subscriptions" button (Value = source kind)
	HitRedditSignIn        // the Reddit editor's "Sign in to Reddit in browser" button
	HitImportSession       // the Instagram/TikTok/X editor's "Import session from Firefox" button

	// Reddit search view actions (Mode == ModeSearch):
	HitSearchReddit        // the sidebar "🔍 Search Reddit" entry (open the search view)
	HitCloseSearch         // the search view's "‹ Back" button (return to the feed)
	HitSearchTab           // switch the results tab — Value = "subreddits"|"posts"
	HitFocusSearchQuery    // focus the search-query input
	HitFocusSearchRegex    // focus the subreddit-results regexp filter input
	HitRunSearch           // run the search for the current query + tab
	HitSubscribeChannel    // subscribe to a channel result — Item.Source = platform, Value = channel handle
	HitSubscribePostSearch // save the post keyword search as a live channel — Value = query
)

type LogEntry

type LogEntry struct {
	When   time.Time
	Method string
	URL    string
	Status int
	Bytes  int64
	Dur    time.Duration
	Err    string
}

LogEntry is one HTTP exchange as the log view needs it. It mirrors internal/httplog.Entry so the ui package stays free of that dependency; the app converts between the two.

type MediaRequest

type MediaRequest struct {
	ID      string
	URL     string
	Created int64
}

MediaRequest names one feed item's remote thumbnail: the item ID the decoded image is keyed by, the URL to fetch it from, and the item's Created time (Unix seconds, UTC; 0 when unknown) so the on-disk media cache can stamp the stored file with the POST's date rather than the download moment.

type Mode

type Mode int

Mode selects which view the scene renders.

const (
	ModeFeed     Mode = iota // the topbar + sidebar + unified feed
	ModeDetail               // a single item's full detail / reading view
	ModeSettings             // the in-canvas preferences editor
	ModeLog                  // the in-canvas HTTP-exchange (Network) log
	ModeAccounts             // the in-canvas per-provider credentials editor
	ModeBrowse               // the in-canvas newsgroup browser / subscribe view
	ModeSearch               // the in-canvas Reddit search / discover view
)

type PrefetchRequest

type PrefetchRequest struct {
	ID    string
	Parts []ReconstructPart
}

PrefetchRequest names a shown Usenet image post to prefetch: an id (the feed item id or a group's release base) and the member articles to fetch.

type ReconstructPart

type ReconstructPart struct {
	MessageID string
	Filename  string
}

ReconstructPart names one article to fetch for a reconstruction: the bare message-id (no "news:" scheme, no angle brackets) and the file it belongs to.

type Scene

type Scene struct {
	W, H int

	Items  []source.Item // the unified feed (merge newest-first before setting)
	Subs   []Subscription
	Active int // selected subscription index, or AllFilter
	Status string

	Scale float64 // display scale (zoom × devicePixelRatio); 0 => 1

	// Feed-loading triggers, driven by the CardList's own edge accumulators.
	// OnReachBottom is wired to the list's OnReachTop (a pull toward the top loads
	// OLDER posts, since the newest sits at the bottom); OnPullRefresh is wired to
	// the list's OnReachBottom (a pull past the newest re-aggregates). Both are
	// nil-safe (only called when installed). infiniteScroll gates whether the
	// next-page trigger is offered.
	OnReachBottom func()
	OnPullRefresh func()

	// Profiles are the named sidebar tabs; the active one supplies Subs.
	Profiles []settings.Profile

	// Optional decoded thumbnails keyed by Item.ID (blitted when present).
	Thumbs map[string]*image.RGBA
	// contains filtered or unexported fields
}

Scene is the mutable aggregator UI state.

func New

func New(w, h int, theme *toolkit.Theme) *Scene

New returns a Scene of the given size with the given theme (system default if nil).

func (*Scene) A11yTree

func (s *Scene) A11yTree() []A11yNode

A11yTree describes what the reader is currently showing, as a flat list in reading order.

It is built from the scene's own laid-out state rather than from a widget tree, because there is no widget tree to walk: every view except the network log composes its widgets inside Draw and discards them. The durable structure is the layout — the same rects HitTest resolves against — which is also the better source: it is what the user can actually perceive and act on.

Tying the two together is deliberate. Anything interactive here carries the rect its hit test uses, so "you can click it" and "a reader can find it" are the same statement; TestA11yNodesAreReachable asserts exactly that by hit-testing every interactive node's centre.

func (*Scene) ActiveFilter

func (s *Scene) ActiveFilter() int

ActiveFilter returns the selected sidebar filter — a subscription index, or AllFilter when no single subscription is selected. Pull-to-refresh reads it so it can re-fetch only the subscription currently in view.

func (*Scene) ActiveProfile

func (s *Scene) ActiveProfile() settings.Profile

ActiveProfile returns the active profile (a safe fallback when empty).

func (*Scene) ActiveProfileIndex

func (s *Scene) ActiveProfileIndex() int

ActiveProfileIndex returns the active profile index.

func (*Scene) AddInputSub

func (s *Scene) AddInputSub()

AddInputSub appends the typed channel (under the selected source) to the edited profile and clears the field. Duplicates are ignored.

func (*Scene) AdvanceAnim

func (s *Scene) AdvanceAnim(n int)

AdvanceAnim advances the animation clock by n frames and marks the scene dirty. A present loop calls it while [Animating] is true; the indeterminate indicator derives its position from the frame counter. n is the number of tick intervals that have elapsed since the last advance, so a loop that throttles its redraws to below the tick rate (advancing every few ticks instead of every one) still turns the spinner at the same real-time speed — it just steps it in larger, less frequent increments. n <= 0 is a no-op, so a spurious call can never rewind or freeze the clock.

func (*Scene) AnimFrame

func (s *Scene) AnimFrame() int

AnimFrame returns the current animation frame counter (for tests).

func (*Scene) Animating

func (s *Scene) Animating() bool

Animating reports whether the scene wants a fresh frame every tick (a loading indicator is moving): a feed refresh, OR a preview image / web-page render in flight (whose spinner must keep spinning). When false the scene is idle and a damage-gated present loop never re-draws — so animation costs nothing when nothing is loading.

func (*Scene) AuthPrompts

func (s *Scene) AuthPrompts() []AuthPrompt

AuthPrompts returns the current in-feed sign-in prompts.

func (*Scene) Backspace

func (s *Scene) Backspace()

Backspace removes the last rune of the focused text field.

func (*Scene) BeginFolderRename

func (s *Scene) BeginFolderRename(name string)

BeginFolderRename starts an inline rename of the folder named name: it seeds a focused toolkit.Entry with the current name (caret at the end) as the edit buffer. An unknown folder name is a no-op.

func (*Scene) BeginPreviewResize

func (s *Scene) BeginPreviewResize()

BeginPreviewResize / EndPreviewResize / DraggingPreview drive the preview pane's left-edge divider drag, mirroring the sidebar divider.

func (*Scene) BeginSidebarResize

func (s *Scene) BeginSidebarResize()

BeginSidebarResize starts a divider drag.

func (*Scene) BiometricUnlock

func (s *Scene) BiometricUnlock() bool

BiometricUnlock reports whether biometric-gated secret storage is enabled.

func (*Scene) BookmarkedURLs

func (s *Scene) BookmarkedURLs() []string

BookmarkedURLs returns the bookmarked URLs in sorted order (stable snapshot for persistence).

func (*Scene) BrowseCaretKey

func (s *Scene) BrowseCaretKey(code string)

BrowseCaretKey forwards a caret-navigation key (toolkit codes "ArrowLeft", "ArrowRight", "Home", "End") to the newsgroup filter, so a typo can be corrected mid-text instead of only backspaced from the end. The caller drives it only while the filter is focused; caret movement does not change the filter result, so the list scroll is left alone.

func (*Scene) BrowseEntry

func (s *Scene) BrowseEntry() *toolkit.SearchEntry

BrowseEntry exposes the filter widget so the app can two-way bind it to the view-model like the topbar search (mvvm.BindField).

func (*Scene) BrowseFocused

func (s *Scene) BrowseFocused() bool

BrowseFocused reports whether the filter field holds keyboard focus.

func (*Scene) BrowseGroups

func (s *Scene) BrowseGroups() []source.GroupInfo

BrowseGroups returns the loaded group list.

func (*Scene) BrowseNodeExpanded

func (s *Scene) BrowseNodeExpanded(name string) bool

BrowseNodeExpanded reports whether the node with the given name is expanded.

func (*Scene) BrowseSelectedNode

func (s *Scene) BrowseSelectedNode() (name string, hasChildren, isGroup, subscribed, ok bool)

BrowseSelectedNode reports the keyboard-selected tree node: its full name, whether it has children (an expandable hierarchy), whether a real group exists at it, and whether that group is already subscribed. ok is false when there is no selectable row.

func (*Scene) Browser

func (s *Scene) Browser() *toolkit.Browser

Browser exposes the embedded web-preview browser so the app can wire its OnNavigate render seam and drive it (Open / Deliver).

func (*Scene) BrowserChromeHidden

func (s *Scene) BrowserChromeHidden() bool

BrowserChromeHidden reports whether the embedded browser's chrome is hidden.

func (*Scene) BrowserFocused

func (s *Scene) BrowserFocused() bool

BrowserFocused reports whether the embedded browser holds keyboard focus.

func (*Scene) BrowserSingleTab

func (s *Scene) BrowserSingleTab() bool

BrowserSingleTab reports whether the embedded browser is in single-tab mode.

func (*Scene) BrowserVM

func (s *Scene) BrowserVM() *tkbind.BrowserVM

BrowserVM exposes the browser's MVVM view-model (observable URL / loading / history-guards and the Back/Forward/Reload commands).

func (*Scene) BrowserZoomInKey

func (s *Scene) BrowserZoomInKey() string

BrowserZoomInKey / BrowserZoomOutKey return the configured base keys as 1-rune strings ("" when unset) for the settings snapshot and the settings editor.

func (*Scene) BrowserZoomOutKey

func (s *Scene) BrowserZoomOutKey() string

func (*Scene) CacheBackend

func (s *Scene) CacheBackend() string

CacheBackend returns the media-cache backend selector ("" / "local" for the built-in disk cache, or a filesystem path to a cache-plugin binary).

func (*Scene) CachePath

func (s *Scene) CachePath() string

CachePath returns the media cache directory.

func (*Scene) CancelFolderRename

func (s *Scene) CancelFolderRename()

CancelFolderRename abandons an in-progress inline rename, discarding the edit buffer and leaving the folder's name unchanged.

func (*Scene) ChannelResults

func (s *Scene) ChannelResults() []source.ChannelResult

ChannelResults / PostResults expose the delivered results (front-ends/tests).

func (*Scene) ClearPendingSource

func (s *Scene) ClearPendingSource(k source.Kind, ch string)

ClearPendingSource clears the pending marker for one source+channel once it has returned (with items or an error).

func (*Scene) ClearSelection

func (s *Scene) ClearSelection()

ClearSelection discards the current selection.

func (*Scene) CloseAccounts

func (s *Scene) CloseAccounts()

CloseAccounts returns to the feed view.

func (*Scene) CloseBrowse

func (s *Scene) CloseBrowse()

CloseBrowse returns to the feed view.

func (*Scene) CloseDetail

func (s *Scene) CloseDetail()

CloseDetail returns from the detail view to the feed.

func (*Scene) CloseLog

func (s *Scene) CloseLog()

CloseLog returns from the Network-log view to the feed.

func (*Scene) CloseSearch

func (s *Scene) CloseSearch()

CloseSearch returns to the feed view.

func (*Scene) CloseSettings

func (s *Scene) CloseSettings()

CloseSettings returns to the feed view.

func (*Scene) CommentsLoading

func (s *Scene) CommentsLoading(id string) bool

CommentsLoading reports whether a comment fetch is in flight for item id.

func (*Scene) CommitCache

func (s *Scene) CommitCache()

CommitCache applies the cache-path buffer.

func (*Scene) CommitCacheBackend

func (s *Scene) CommitCacheBackend()

CommitCacheBackend applies the cache-backend buffer: blank / "local" keeps the built-in disk cache, a filesystem path selects an external cache plugin. The change takes effect on the next launch (the backend is chosen at startup), like the plugins directory.

func (*Scene) CommitCacheSize

func (s *Scene) CommitCacheSize()

CommitCacheSize applies the media-cache size-limit (MB) buffer: on a valid integer it sets the limit, clamped to [MinMediaCacheMB, MaxMediaCacheMB] (0 → DefaultMediaCacheMB); on a parse error the current value is kept. Either way the buffer is re-synced to the resulting value so the field shows the real, clamped limit rather than the raw typed text.

func (*Scene) CommitFolderRename

func (s *Scene) CommitFolderRename()

CommitFolderRename applies the inline rename when the edit buffer is non-blank and actually changed, then clears the rename state. The window layer persists the settings afterwards (App.PersistSettings), matching the other folder edits.

func (*Scene) CommitRename

func (s *Scene) CommitRename()

CommitRename applies the rename buffer to the edited profile (blank ignored).

func (*Scene) CommitZoomKeys

func (s *Scene) CommitZoomKeys()

CommitZoomKeys applies the two zoom-key buffers (each a single printable rune) to the browser zoom bindings. A blank buffer leaves that binding unchanged.

func (*Scene) ContextMenuActive

func (s *Scene) ContextMenuActive() bool

ContextMenuActive reports whether a context menu is currently popped up, so the input path can route events to it instead of the scene beneath.

func (*Scene) ContextMenuEvent

func (s *Scene) ContextMenuEvent(ev toolkit.Event)

ContextMenuEvent forwards one input event to the open context menu and drops the menu once it has closed itself (an item fired, a click landed outside, or Escape was pressed). It is a no-op when no menu is open.

func (*Scene) Copy

func (s *Scene) Copy() bool

Copy copies the current context to the system clipboard, reporting whether anything was copied. Precedence, most specific first: the focused web-preview address bar (so Cmd/Ctrl+C copies the URL you are editing, with a select-all highlight); then a non-empty reading-view text selection (the user picked exactly what to copy); then the whole article being read (detail item, else the preview item) as plain text.

func (*Scene) DeleteFolder

func (s *Scene) DeleteFolder(name string)

DeleteFolder removes the folder named name; its subscriptions return to the sidebar root. An unknown name is a no-op.

func (*Scene) DeleteProfile

func (s *Scene) DeleteProfile(i int)

DeleteProfile removes profile i (never below one profile), re-clamps the active + edited indices, and rebuilds the sidebar.

func (*Scene) Detail

func (s *Scene) Detail() source.Item

Detail returns the item currently open in the detail view.

func (*Scene) DismissPreviewOnSwitch

func (s *Scene) DismissPreviewOnSwitch() bool

DismissPreviewOnSwitch reports the current dismiss-preview-on-switch setting.

func (*Scene) Downloads

func (s *Scene) Downloads() []DownloadItem

Downloads returns the current download list.

func (*Scene) DraggingPreview

func (s *Scene) DraggingPreview() bool

func (*Scene) DraggingSidebar

func (s *Scene) DraggingSidebar() bool

DraggingSidebar reports whether the divider is being dragged.

func (*Scene) Draw

func (s *Scene) Draw(buf []byte)

Draw paints the whole scene into buf (s.W*s.H*4 RGBA bytes).

func (*Scene) EditedAccounts

func (s *Scene) EditedAccounts() []settings.Account

EditedAccounts projects the editable buffers back into persisted accounts, in the schema's stable order, dropping providers (and individual fields) whose values are blank so an untouched provider leaves no empty account behind.

func (*Scene) EndPreviewResize

func (s *Scene) EndPreviewResize()

func (*Scene) EndSidebarResize

func (s *Scene) EndSidebarResize()

EndSidebarResize ends a divider drag.

func (*Scene) FeedAnimating

func (s *Scene) FeedAnimating() bool

FeedAnimating reports whether the feed CardList's fetch strips are spinning (a fetch is in flight), so a gated present loop keeps ticking the feed.

func (*Scene) FeedCardList

func (s *Scene) FeedCardList() *virtual.CardList[source.Item]

FeedCardList exposes the feed's virtual.CardList (front-ends/tests drive and inspect it: bounds, scroll, events, animation).

func (*Scene) FeedClickAt

func (s *Scene) FeedClickAt(x, y int)

FeedClickAt routes a screen-space click into the feed. A click over an EXPANDED group card's member row previews that member article directly (its own item, like the old member row) and stops there; every other click goes to the CardList (selecting the card under the pointer, translating to the list's widget-local coordinates). A click above the list origin (e.g. on a pinned banner) maps to a negative local Y the CardList treats as no card.

func (*Scene) FeedEvent

func (s *Scene) FeedEvent(ev toolkit.Event)

FeedEvent routes one input event into the feed CardList (wheel scroll, selection keys, or a widget-local click), setting the keyboard-origin flag around it so the select hook can tell a cursor move from a click. localY is only read for a click (feed-relative). It is the input seam that replaces the hand-drawn feed's wheel / arrow / hit-test handling.

func (*Scene) FeedGeom

func (s *Scene) FeedGeom() (x, w int)

feedGeom returns the feed list's left origin and width, accounting for the sidebar on the left and the preview pane on the right. Every feed layout / draw / hit-test path goes through it so the three stay consistent. FeedGeom is the feed pane's horizontal extent in scene pixels: where the cards, the scrollbar and the empty-state label all live. Exported so a caller -- a test, a host laying something over the pane -- can ask for the geometry the renderer actually uses instead of recomputing it and being subtly wrong.

func (*Scene) FeedModelItems

func (s *Scene) FeedModelItems() []source.Item

FeedModelItems returns the feed model's current items (oldest→newest), a stable snapshot for tests/inspection.

func (*Scene) FeedScrollOffset

func (s *Scene) FeedScrollOffset() int

FeedScrollOffset exposes the feed CardList's current pixel scroll offset. The window-app layer, the scrollbar and the a11y tree read it to map feed content to screen space.

func (*Scene) FeedScrollToBottom

func (s *Scene) FeedScrollToBottom()

FeedScrollToBottom opens the feed at its newest (bottom) post — the app calls it after switching tabs / filters (it is also armed automatically by SetActive / SetItems).

func (*Scene) FeedTick

func (s *Scene) FeedTick(dt float64)

FeedTick advances the feed CardList's pull-to-fetch spinners by dt seconds through the toolkit animation tree, and reports whether the feed still wants frames. A present loop drives it while the feed is fetching.

func (*Scene) FeedWheel

func (s *Scene) FeedWheel(dy int)

FeedWheel routes a device-pixel wheel delta into the feed CardList as a signed whole-row scroll (magnitude ≥ 1 for any non-zero nudge, so a small notch still advances a card). The CardList owns the scroll anchor and the infinite-scroll / pull-to-refresh accumulators, so this is the only feed-wheel path.

func (*Scene) FinishPreviewImage

func (s *Scene) FinishPreviewImage(id string, img *image.RGBA)

FinishPreviewImage stores the reconstructed image (when non-nil) and clears the pane's loading state if the fetch was for the item still being previewed. The image is keyed by id, so a late result for a since-deselected item is cached (for its card thumbnail) without disturbing the current selection.

func (*Scene) Focus

func (s *Scene) Focus() Focus

Focus reports which settings text field currently has keyboard focus.

func (*Scene) FocusAccountField

func (s *Scene) FocusAccountField(key string)

FocusAccountField gives keyboard focus to a credential field.

func (*Scene) FocusBrowseFilter

func (s *Scene) FocusBrowseFilter(v bool)

FocusBrowseFilter gives (or removes) keyboard focus to the filter field.

func (*Scene) FocusCache

func (s *Scene) FocusCache()

func (*Scene) FocusCacheBackend

func (s *Scene) FocusCacheBackend()

FocusCacheBackend gives keyboard focus to the cache-backend (plugin path) field.

func (*Scene) FocusCacheSize

func (s *Scene) FocusCacheSize()

FocusCacheSize gives keyboard focus to the media-cache size-limit (MB) field.

func (*Scene) FocusChannel

func (s *Scene) FocusChannel()

func (*Scene) FocusRename

func (s *Scene) FocusRename()

FocusRename / FocusChannel / FocusCache give keyboard focus to a text field.

func (*Scene) FocusSearch

func (s *Scene) FocusSearch(v bool)

FocusSearch gives (or removes) keyboard focus to the search field. A focus change dismisses the copied-highlight.

func (*Scene) FocusSearchQuery

func (s *Scene) FocusSearchQuery(v bool)

FocusSearchQuery gives (or removes) keyboard focus to the query field. Focusing the query blurs the regexp field so keystrokes never land in both.

func (*Scene) FocusSearchRegex

func (s *Scene) FocusSearchRegex(v bool)

FocusSearchRegex gives (or removes) keyboard focus to the regexp filter field.

func (*Scene) FocusSettingsField

func (s *Scene) FocusSettingsField(f Focus)

FocusSettingsField gives keyboard focus to a settings field by name. The windowapp Handler calls it before commitSettingsField so a native entry's Enter commits the field the person was editing even if no keystroke set focus first.

func (*Scene) FocusZoomIn

func (s *Scene) FocusZoomIn()

FocusZoomIn / FocusZoomOut give keyboard focus to a zoom-key field.

func (*Scene) FocusZoomOut

func (s *Scene) FocusZoomOut()

func (*Scene) FolderCollapsed

func (s *Scene) FolderCollapsed(name string) bool

FolderCollapsed reports whether the folder named name is currently collapsed (for tests / front-ends).

func (*Scene) FolderNames

func (s *Scene) FolderNames() []string

FolderNames returns the folder names in their current order (for the "Move to folder ▸" submenu).

func (*Scene) FolderRenaming

func (s *Scene) FolderRenaming() bool

FolderRenaming reports whether a folder is currently being renamed inline.

func (*Scene) FollowImportKinds

func (s *Scene) FollowImportKinds() []source.Kind

FollowImportKinds returns the source kinds currently marked follow-capable, in lexical order — the set Scene.SetFollowImportKinds last recorded. It lets a caller (and tests) read back which providers offer the "Import subscriptions" action without going through the accounts-editor hit test.

func (*Scene) ForwardBrowserClick

func (s *Scene) ForwardBrowserClick(x, y int) bool

ForwardBrowserClick forwards a click at screen coords (x, y) into the embedded browser when the web preview is active and the point lands inside the widget, translating to widget-local coordinates. It returns true when the browser consumed the click (so the caller does no further routing). A click outside the widget (or when the browser is not shown) blurs the browser's keyboard focus and returns false so the caller falls through to its own hit-testing.

func (*Scene) ForwardBrowserDrag

func (s *Scene) ForwardBrowserDrag(x, y int) bool

ForwardBrowserDrag forwards pointer motion at screen coords (x, y) into the embedded browser as an EventMouseDrag, but ONLY while a browser press is active (a prior ForwardBrowserClick consumed a press inside the widget). This is what lets a grabbed scrollbar thumb — vertical OR horizontal — follow the mouse. It returns true when the drag was routed to the browser. A drag with no active browser press is ignored (returns false) so the caller keeps its own drag handling (e.g. text selection or a pane-divider resize).

func (*Scene) ForwardBrowserKey

func (s *Scene) ForwardBrowserKey(name string, r rune) bool

ForwardBrowserKey forwards an editing key or printable rune into the embedded browser's address field when the web preview is active and the browser holds keyboard focus (a prior click landed inside it). Enter commits/navigates the address, Backspace edits it, and a printable rune types into it. Returns true when the key was routed to the browser.

func (*Scene) ForwardBrowserRelease

func (s *Scene) ForwardBrowserRelease(x, y int)

ForwardBrowserRelease forwards a mouse-button release into the embedded browser when the web preview is active, so a toolbar button pressed on the preceding click clears its press-feedback face. Coordinates are screen-space (translated to widget-local); a release anywhere clears the press.

func (*Scene) ForwardBrowserScroll

func (s *Scene) ForwardBrowserScroll(dy int) bool

ForwardBrowserScroll forwards a wheel delta (device px) into the embedded browser when the web preview is active and the pointer is over the widget, converting pixels to the widget's row-based scroll intent. Returns true when the browser consumed the wheel. It is the plain (unshifted) wheel path — a convenience over ForwardBrowserScrollShift for callers with no modifier state.

func (*Scene) ForwardBrowserScrollShift

func (s *Scene) ForwardBrowserScrollShift(dy int, shift bool) bool

ForwardBrowserScrollShift is ForwardBrowserScroll with the Shift modifier: when shift is true the wheel is delivered to the browser as a shifted EventScroll, which the toolkit Browser interprets as HORIZONTAL scrolling (the vertical bar has a plain-wheel path; the horizontal bar rides shift+wheel). The drag path (ForwardBrowserDrag) already moves either scrollbar thumb; this adds the wheel affordance once a back-end can report the Shift state on a scroll event.

func (*Scene) GIFPlaying

func (s *Scene) GIFPlaying() bool

GIFPlaying reports whether an animated GIF is currently being driven into the preview (front-ends/tests observe it).

func (*Scene) GroupExpanded

func (s *Scene) GroupExpanded(base string) bool

GroupExpanded reports whether the Usenet post group with the given release base is currently expanded (its member parts listed beneath the header).

func (*Scene) GroupParts

func (s *Scene) GroupParts(base string) ([]ReconstructPart, bool)

GroupParts returns the member articles of the currently-displayed group with the given base, ready to hand to the Usenet provider's Reconstruct. It reports false when no such group is shown.

func (*Scene) GroupPreviewItem

func (s *Scene) GroupPreviewItem(base string) (source.Item, bool)

GroupPreviewItem synthesises a source.Item to preview a Usenet multipart post (a group card, which is not itself a single Item): its release base as title, a declared image attachment so the pane reserves the image box, and the id set to the base so the app can key the reconstructed thumbnail by it. Reports false when no shown group has that base.

func (*Scene) HasGroupStats

func (s *Scene) HasGroupStats(name string) bool

HasGroupStats reports whether a sampled estimate is already cached for name (the app checks it before kicking off a scan, avoiding a re-scan).

func (*Scene) HasSelection

func (s *Scene) HasSelection() bool

HasSelection reports whether a non-empty text selection is active.

func (*Scene) HasThumb

func (s *Scene) HasThumb(id string) bool

HasThumb reports whether a decoded image is cached for the item id.

func (*Scene) HitTest

func (s *Scene) HitTest(x, y int) Hit

HitTest maps a click at (x, y) to an action.

func (*Scene) ImagePrefetch

func (s *Scene) ImagePrefetch() []PrefetchRequest

ImagePrefetch returns a request for every shown Usenet post likely to carry an image — a multipart group (binary post) and a standalone article whose subject names an image file — so the app can download their thumbnails in parallel.

func (*Scene) InfiniteScroll

func (s *Scene) InfiniteScroll() bool

InfiniteScroll reports whether the bottom-of-feed next-page trigger is enabled.

func (*Scene) InvalidateBrowse

func (s *Scene) InvalidateBrowse()

InvalidateBrowse resets the scroll after the filter binder writes BrowseEntry.Text directly; passed as mvvm.BindField's invalidate hook.

func (*Scene) InvalidateSearch

func (s *Scene) InvalidateSearch()

InvalidateSearch bumps the damage sequence after the search binder writes SearchEntry.Text directly (bypassing SetSearch). The app's search binder passes it as mvvm.BindField's invalidate hook so a ViewModel-originated search change repaints the topbar (whose sprite cache keys on the widget text).

func (*Scene) IsBookmarked

func (s *Scene) IsBookmarked(url string) bool

IsBookmarked reports whether url is in the bookmark set.

func (*Scene) IsDownloadQueued

func (s *Scene) IsDownloadQueued(id string) bool

IsDownloadQueued reports whether id has an in-flight (queued or active) download — the checked state of its group card checkbox.

func (*Scene) IsPendingSub

func (s *Scene) IsPendingSub(k source.Kind, ch string) bool

IsPendingSub reports whether the given source+channel is still loading.

func (*Scene) IsSubscribed

func (s *Scene) IsSubscribed(k source.Kind, channel string) bool

IsSubscribed reports whether the active profile already subscribes to the given source+channel (case-insensitive on the channel).

func (*Scene) LayoutPreview

func (s *Scene) LayoutPreview()

LayoutPreview lays out the preview pane on demand, outside a Draw. The app calls it right after a selection changes so the embedded browser has its real content bounds (and thus its real render width) BEFORE it fires a web-page open: without it the first open reads the stale/empty bounds left by the prior frame and renders — and caches — the page at width 0, so revisiting it later (now measured at the real width) misses the render cache and re-fetches. It is idempotent — the next Draw recomputes the same layout — so calling it early is harmless.

func (*Scene) Loading

func (s *Scene) Loading() bool

Loading reports whether a refresh is in progress.

func (*Scene) LoadingProgress

func (s *Scene) LoadingProgress() (done, total int)

LoadingProgress returns how many sources have returned and the total.

func (*Scene) LogEntries

func (s *Scene) LogEntries() []LogEntry

LogEntries exposes the current log entries (for front-ends/tests).

func (*Scene) MediaCacheMB

func (s *Scene) MediaCacheMB() int

MediaCacheMB returns the media cache size limit, in MB.

func (*Scene) MediaPrefetch

func (s *Scene) MediaPrefetch() []MediaRequest

MediaPrefetch lists the remote thumbnails the currently shown feed is still missing: one per item that declares media, names it over http(s), and has no decoded thumbnail yet.

Usenet posts are excluded — their images arrive over NNTP through Scene.ImagePrefetch, which was the ONLY path that ever populated Thumbs. So every card from every other source drew the "image"/"video" placeholder forever, however well its provider had reported the media. This is the list the app fetches to close that gap.

func (*Scene) Mode

func (s *Scene) Mode() Mode

Mode reports whether the feed or the detail view is showing.

func (*Scene) MouseMove

func (s *Scene) MouseMove(x, y int)

MouseMove applies a divider drag: while dragging, the pointer's x becomes the new sidebar width (clamped when read). It is a no-op otherwise, so front-ends can forward every pointer move unconditionally.

func (*Scene) MoveSubToFolder

func (s *Scene) MoveSubToFolder(sub int, folderName string)

MoveSubToFolder moves the subscription at sub index into the folder named folderName (creating the folder if it does not yet exist), removing the subscription from any other folder first. A bad sub index is a no-op.

func (*Scene) NativeControls

func (s *Scene) NativeControls() []toolkit.NativeControl

NativeControls returns the native controls accumulated over the last Draw, in visual order. It is what the windowapp Handler hands the platform through the application Surface's Controls field.

func (*Scene) NativeHit

func (s *Scene) NativeHit(key string) (Hit, bool)

NativeHit returns the Hit a native button's activation should run, if the key names one. The windowapp Handler uses it to give each button descriptor an OnActivate that dispatches through the same path a click would.

func (*Scene) NativeSettingsCommit

func (s *Scene) NativeSettingsCommit(key string) (Focus, bool)

NativeSettingsCommit reports the settings field a native entry's Enter should commit, if the key names one. The windowapp Handler uses it to give the descriptor an OnActivate that focuses that field and runs commitSettingsField — the same path a keyboard Enter takes when the drawn field has focus.

func (*Scene) NavBrowse

func (s *Scene) NavBrowse(dir int)

NavBrowse moves the keyboard selection dir rows through the visible tree (dir<0 up, dir>0 down), clamped to the current rows, and scrolls it into view.

func (*Scene) NewProfile

func (s *Scene) NewProfile()

NewProfile appends a fresh profile and selects it for editing.

func (*Scene) NewSidebarFolder

func (s *Scene) NewSidebarFolder(base string) string

NewSidebarFolder creates a new (empty) folder with a unique name derived from base (blank → "New Folder") and returns its final name.

func (*Scene) OpenAccounts

func (s *Scene) OpenAccounts()

OpenAccounts enters the credentials editor (defaulting to Reddit, the reason the feature exists).

func (*Scene) OpenBrowse

func (s *Scene) OpenBrowse()

OpenBrowse enters the newsgroup browser view.

func (*Scene) OpenContextMenu

func (s *Scene) OpenContextMenu(m *toolkit.ContextMenu, x, y int)

OpenContextMenu pops the given toolkit ContextMenu up at (x, y). The menu is bounded to the whole surface so it clamps itself on screen and treats a click outside its body as a dismissal. The scene owns none of the menu's behaviour — it only holds it so Draw can paint it and events can reach it.

func (*Scene) OpenDetail

func (s *Scene) OpenDetail(it source.Item)

OpenDetail switches to the in-app reading view for it (instead of a browser).

func (*Scene) OpenLog

func (s *Scene) OpenLog()

OpenLog enters the Network-log view.

func (*Scene) OpenSearch

func (s *Scene) OpenSearch()

OpenSearch enters the Reddit search view, resetting the scroll and focus.

func (*Scene) OpenSettings

func (s *Scene) OpenSettings()

OpenSettings enters the preferences view, editing the active profile.

func (*Scene) PendingCount

func (s *Scene) PendingCount() int

PendingCount returns how many sources are still loading.

func (*Scene) PostResults

func (s *Scene) PostResults() []source.Item

func (*Scene) PrefetchWebURLs

func (s *Scene) PrefetchWebURLs(it source.Item, n int) []string

PrefetchWebURLs returns up to n distinct web-renderable target URLs for the feed items immediately adjacent to it (previous, then next, then the ones just beyond), for background pre-rendering so selecting a neighbour is instant. It has no side effects — it does not scroll or change the selection, unlike [Scene.NavItem]. Items with no web target (Usenet posts, text-only posts) are skipped; a mismatch (it not in the feed) yields nothing.

func (*Scene) PreviewComments

func (s *Scene) PreviewComments(id string) ([]source.Comment, bool)

PreviewComments returns the fetched comment thread for item id and whether a fetch has completed (an entry exists, possibly empty). Front-ends/tests use it to observe what the app delivered.

func (*Scene) PreviewFocusable

func (s *Scene) PreviewFocusable() bool

PreviewFocusable reports whether the preview pane can currently take keyboard focus: it is laid out (visible) and has a selected item to scroll.

func (*Scene) PreviewFocused

func (s *Scene) PreviewFocused() bool

PreviewFocused reports whether the preview pane holds keyboard focus (so the scroll keys drive it rather than the feed card list).

func (*Scene) PreviewItem

func (s *Scene) PreviewItem() (source.Item, bool)

PreviewItem returns the previewed item and whether one is selected.

func (*Scene) PreviewOpenButton

func (s *Scene) PreviewOpenButton() (toolkit.Rect, bool)

PreviewOpenButton returns the preview pane's "Open" button rect and whether it is currently shown (an item is selected and the pane is visible). It reflects the last layout; front-ends/tests call it after a Draw/HitTest.

func (*Scene) PreviewTextScale

func (s *Scene) PreviewTextScale() float64

PreviewTextScale returns the current reader/preview text zoom multiplier.

func (*Scene) RemoveSub

func (s *Scene) RemoveSub(prof, sub int)

RemoveSub drops subscription sub from profile prof.

func (*Scene) RemoveSubFromFolder

func (s *Scene) RemoveSubFromFolder(sub int)

RemoveSubFromFolder takes the subscription at sub index out of whatever folder holds it, so it returns to the sidebar root. A bad index (or a subscription in no folder) is a no-op beyond the redraw.

func (*Scene) RenameFolder

func (s *Scene) RenameFolder(old, newName string)

RenameFolder renames the folder named old to newName (unique-ified), carrying its collapse state across. A blank new name, an unknown old name, or old == new is a no-op.

func (*Scene) RenamingFolder

func (s *Scene) RenamingFolder() string

RenamingFolder returns the folder currently being renamed inline, or "" when none is (for tests / front-ends).

func (*Scene) Resize

func (s *Scene) Resize(w, h int)

Resize updates the surface size, clamped to the minimum.

func (*Scene) Rev

func (s *Scene) Rev() int

Rev returns the current damage/commit sequence. It advances whenever any state that affects the rendered frame changes, so a double-buffered present loop can skip redundant redraws/uploads.

func (*Scene) Scroll

func (s *Scene) Scroll(dy int)

Scroll adjusts the vertical scroll of whichever view is showing, clamped to its content height.

func (*Scene) ScrollPreviewKey

func (s *Scene) ScrollPreviewKey(dir int, page bool) bool

ScrollPreviewKey scrolls the focused preview pane by one keyboard step: dir<0 scrolls up, dir>0 down; page requests a (near) full viewport move (PageUp / PageDown / Space). For a web-linked item it forwards to the embedded browser; for a text/image item it nudges previewScroll. It consumes the key (returns true) whenever the pane holds focus — even with nothing to scroll — so the key does not fall through to the feed selection. It is a no-op (false) when the pane is not focused or dir is 0.

func (*Scene) ScrollY

func (s *Scene) ScrollY() int

ScrollY exposes the feed's current scroll offset (used by the window app layer). It reads the virtual.CardList's pixel scroll offset.

func (*Scene) Search

func (s *Scene) Search() string

Search returns the current filter text (the search widget's text).

func (*Scene) SearchEntry

func (s *Scene) SearchEntry() *toolkit.SearchEntry

SearchEntry exposes the topbar's search widget so the app can two-way bind vm.Search to it (mvvm.BindField(&entry.Text, &entry.OnChange)).

func (*Scene) SearchFocused

func (s *Scene) SearchFocused() bool

SearchFocused reports whether the search field has keyboard focus.

func (*Scene) SearchLoading

func (s *Scene) SearchLoading() bool

SearchLoading reports whether a search request is in flight.

func (*Scene) SearchQuery

func (s *Scene) SearchQuery() string

SearchQuery returns the trimmed query text.

func (*Scene) SearchQueryFocused

func (s *Scene) SearchQueryFocused() bool

SearchQueryFocused / SearchRegexFocused report which field holds focus.

func (*Scene) SearchRegex

func (s *Scene) SearchRegex() string

SearchRegex returns the trimmed subreddit-results regexp filter text.

func (*Scene) SearchRegexFocused

func (s *Scene) SearchRegexFocused() bool

func (*Scene) SearchStatus

func (s *Scene) SearchStatus() string

SearchStatus returns the current search status line.

func (*Scene) SearchTabPosts

func (s *Scene) SearchTabPosts() bool

SearchTabPosts reports whether the post-search tab is active (else subreddits).

func (*Scene) SelectAccount

func (s *Scene) SelectAccount(k source.Kind)

SelectAccount picks which provider the editor operates on.

func (*Scene) SelectEditProfile

func (s *Scene) SelectEditProfile(i int)

SelectEditProfile picks which profile the editor operates on (clamped) and seeds the rename buffer with its name.

func (*Scene) SelectKind

func (s *Scene) SelectKind(k source.Kind)

SelectKind records which source the add-subscription palette will use.

func (*Scene) SelectPreview

func (s *Scene) SelectPreview(it source.Item)

SelectPreview loads it into the preview pane (clicking a feed item), resetting the pane scroll. The app calls SetPreviewLoading before an async image fetch.

func (*Scene) SelectableAt

func (s *Scene) SelectableAt(x, y int) bool

SelectableAt reports whether a press at screen point (x, y) should begin a text selection: the reading surfaces whose runs are collected each frame — the full-screen detail view, or the preview pane's text summary (not the embedded web browser, which handles its own selection). A front-end consults it before starting a drag-selection.

func (*Scene) SelectedAccount

func (s *Scene) SelectedAccount() source.Kind

SelectedAccount reports which provider the accounts editor is operating on.

func (*Scene) SelectedBrowseGroup

func (s *Scene) SelectedBrowseGroup() (string, bool)

SelectedBrowseGroup returns the name of the group under the browser's keyboard selection, and whether the selected row is a group (not a hierarchy node). The app uses it to scan the focused group's content mix.

func (*Scene) SelectionBegin

func (s *Scene) SelectionBegin(x, y int)

SelectionBegin starts a text selection at screen point (x, y) over the runs of the reading surface drawn last frame. A press with no drag leaves the selection empty (so a plain click clears any prior selection).

func (*Scene) SelectionEnd

func (s *Scene) SelectionEnd()

SelectionEnd finishes a drag; the selection stays in place for a copy.

func (*Scene) SetAccountField

func (s *Scene) SetAccountField(k source.Kind, key, value string)

SetAccountField writes value into provider k's credential buffer for key, lazily allocating. It lets the app inject a credential obtained outside the editor — e.g. a reddit_session cookie imported from Firefox — so the editor reflects it and a commit persists it.

func (*Scene) SetAccounts

func (s *Scene) SetAccounts(accts []settings.Account)

SetAccounts seeds the editable credential buffers from the persisted accounts.

func (*Scene) SetActive

func (s *Scene) SetActive(i int)

SetActive selects the sidebar filter (a subscription index, or AllFilter). A filter switch opens the (re-filtered) feed at its newest post.

func (*Scene) SetActiveProfile

func (s *Scene) SetActiveProfile(i int)

SetActiveProfile switches the active profile (clamped) and rebuilds the sidebar subscriptions from it.

func (*Scene) SetAuthPrompts

func (s *Scene) SetAuthPrompts(p []AuthPrompt)

SetAuthPrompts replaces the set of providers that need sign-in/configuration, as collected by the app after an aggregate. The feed renders one clickable banner per prompt above the cards; an empty slice removes the banner.

func (*Scene) SetBiometricUnlock

func (s *Scene) SetBiometricUnlock(v bool)

SetBiometricUnlock records whether the account secrets are stored/read behind a biometric user-presence gate (Touch ID on macOS). The app persists it and re-writes the secrets so the gate takes effect.

func (*Scene) SetBookmarked

func (s *Scene) SetBookmarked(url string, on bool)

SetBookmarked adds or removes url from the bookmark set (lazily allocating).

func (*Scene) SetBookmarks

func (s *Scene) SetBookmarks(urls []string)

SetBookmarks seeds the bookmarked-URL set from the persisted settings.

func (*Scene) SetBrowseGroups

func (s *Scene) SetBrowseGroups(groups []source.GroupInfo)

SetBrowseGroups stores the server's full active group list (name + estimated post count — the browse view's data source) and invalidates the cached tree so the next layout rebuilds it.

func (*Scene) SetBrowserChromeHidden

func (s *Scene) SetBrowserChromeHidden(v bool)

SetBrowserChromeHidden hides (v=true) or shows the embedded browser's toolbar + tab strip, so a page can render chrome-free in the preview.

func (*Scene) SetBrowserSingleTab

func (s *Scene) SetBrowserSingleTab(v bool)

SetBrowserSingleTab selects the embedded browser's tab mode: single-tab reuses one tab per page (no strip), multi-tab (the default) keeps a switchable strip.

func (*Scene) SetBrowserZoomKeys

func (s *Scene) SetBrowserZoomKeys(in, out string)

SetBrowserZoomKeys configures the base runes that, held with a command-style modifier (Ctrl/Cmd), zoom the embedded web preview in / out. A blank or multi-rune string leaves the corresponding binding unchanged, so a missing persisted value keeps the '='/'-' default.

func (*Scene) SetCacheBackend

func (s *Scene) SetCacheBackend(b string)

SetCacheBackend records the media-cache backend selector.

func (*Scene) SetCachePath

func (s *Scene) SetCachePath(p string)

SetCachePath records the media cache directory.

func (*Scene) SetChannelResults

func (s *Scene) SetChannelResults(rs []source.ChannelResult)

SetChannelResults delivers channel-discovery results (subreddits, accounts, hashtags — whatever the searched platforms returned), switches to the channels tab, clears loading and resets the scroll.

func (*Scene) SetComments

func (s *Scene) SetComments(id string, comments []source.Comment)

SetComments attaches the flattened comment thread for an item id (the app delivers it after an async fetch) and clears that id's loading marker, so the preview pane paints the thread in place of the "loading comments" line. A nil slice is stored as an explicit "fetched, none" so the pane shows "0 comments" rather than spinning.

func (*Scene) SetCommentsLoading

func (s *Scene) SetCommentsLoading(id string, v bool)

SetCommentsLoading marks (v=true) or clears that a comment fetch is in flight for item id, so the pane shows a loading line beneath the post until SetComments lands.

func (*Scene) SetDismissPreviewOnSwitch

func (s *Scene) SetDismissPreviewOnSwitch(v bool)

SetDismissPreviewOnSwitch controls whether switching to another subscription/group closes the preview pane (see SetActive).

func (*Scene) SetDownloads

func (s *Scene) SetDownloads(items []DownloadItem)

SetDownloads replaces the download list (the app pushes the full snapshot).

func (*Scene) SetFeedSelectHook

func (s *Scene) SetFeedSelectHook(f func(it source.Item, viaKeyboard bool))

SetFeedSelectHook installs the app callback fired when a feed card is selected (click or keyboard). Its bool argument is true when the selection came from a keyboard cursor move, so the app can debounce a web render. Passing nil restores the scene's own preview-only behaviour.

func (*Scene) SetFollowImportKinds

func (s *Scene) SetFollowImportKinds(kinds []source.Kind)

SetFollowImportKinds records which source kinds can import the connected account's follows (their provider implements source.FollowImporter), so the accounts editor shows the "Import subscriptions" affordance for exactly those. The app calls it whenever it (re)builds the provider registry.

func (*Scene) SetGIFPlaying

func (s *Scene) SetGIFPlaying(v bool)

SetGIFPlaying marks whether the app is currently driving an animated GIF's frames into the embedded browser. While true, Animating stays true so the present loop keeps ticking and the GIF advances; the app clears it when the preview navigates away or closes.

func (*Scene) SetGroupStats

func (s *Scene) SetGroupStats(name string, st source.GroupStats)

SetGroupStats stores the sampled content-mix estimate for a group and repaints so its browser row picks up the "~B% bin · ~I% img" suffix.

func (*Scene) SetInfiniteScroll

func (s *Scene) SetInfiniteScroll(v bool)

SetInfiniteScroll enables or disables fetching the next page of posts when the feed list is scrolled to the bottom (the OnReachBottom trigger). Pull-to-refresh is always on and unaffected — only the bottom trigger is gated.

func (*Scene) SetItems

func (s *Scene) SetItems(items []source.Item)

SetItems replaces the feed (caller merges/sorts newest-first).

func (*Scene) SetLoading

func (s *Scene) SetLoading(active bool, done, total int)

SetLoading sets the live-loading feedback. active marks a refresh as in progress (which drives the in-feed indicator and the per-frame animation); done/total report how many sources have returned. Turning loading off clears the pending-source markers so nothing is left dimmed.

func (*Scene) SetLogSource

func (s *Scene) SetLogSource(fn func() []LogEntry)

SetLogSource installs the callback the log view queries each frame for the current exchanges (newest-first). Nil leaves the view empty.

func (*Scene) SetMediaCacheMB

func (s *Scene) SetMediaCacheMB(mb int)

SetMediaCacheMB records the media cache size limit, in MB.

func (*Scene) SetPendingSources

func (s *Scene) SetPendingSources(subs []source.Subscription)

SetPendingSources marks every given subscription as still-loading, so the sidebar rows for those sources show a pending marker until they return.

func (*Scene) SetPostResults

func (s *Scene) SetPostResults(items []source.Item)

SetPostResults delivers post-search results, switches to the posts tab, clears loading and resets the scroll.

func (*Scene) SetPreviewLoading

func (s *Scene) SetPreviewLoading(v bool)

SetPreviewLoading marks (or clears) that an image fetch is in flight for the previewed item, so the pane shows a spinner until SetThumb lands.

func (*Scene) SetPreviewTextScale

func (s *Scene) SetPreviewTextScale(f float64)

SetPreviewTextScale sets the reader/preview text zoom (a text post's title, body and meta, and the header of any previewed post), clamped to the supported range, and requests a redraw. The A−/A+ preview-toolbar pills drive it.

func (*Scene) SetProfiles

func (s *Scene) SetProfiles(profiles []settings.Profile, active int)

SetProfiles replaces the profile list and selects the active index (clamped), deriving the sidebar subscriptions from the newly-active profile.

func (*Scene) SetRenderCacheStats

func (s *Scene) SetRenderCacheStats(fn func() (pages, bytes int))

SetRenderCacheStats installs the callback the Settings view queries for the web-preview render cache's current size (pages, bytes). Nil shows an empty cache in the caption.

func (*Scene) SetScale

func (s *Scene) SetScale(f float64)

SetScale sets the display scale, clamped to [MinZoom, MaxZoom].

func (*Scene) SetSearch

func (s *Scene) SetSearch(v string)

SetSearch replaces the filter text on the search widget.

func (*Scene) SetSearchLoading

func (s *Scene) SetSearchLoading(v bool)

SetSearchLoading marks whether a Reddit search request is in flight (folded into Animating so the spinner keeps ticking) and clears the status while busy.

func (*Scene) SetSearchStatus

func (s *Scene) SetSearchStatus(v string)

SetSearchStatus sets an informational/error line for the search view (e.g. "No results" or a provider message) and clears the loading flag.

func (*Scene) SetSearchTab

func (s *Scene) SetSearchTab(posts bool)

SetSearchTab selects the subreddit-discovery or post-search results tab and resets the result scroll.

func (*Scene) SetSeen

func (s *Scene) SetSeen(seen map[string]int)

SetSeen records the per-subscription seen markers (from the app's persisted store) and re-renders the sidebar so the unseen counts update.

func (*Scene) SetSidebarFolders

func (s *Scene) SetSidebarFolders(folders []settings.Folder)

SetSidebarFolders replaces the virtual-folder set (seeded from the persisted settings at startup) and re-renders the sidebar.

func (*Scene) SetSignInBrowser

func (s *Scene) SetSignInBrowser(name string)

SetSignInBrowser records the sign-in browser choice (unrecognised values fall back to the default so the picker can never persist a bad value).

func (*Scene) SetStatus

func (s *Scene) SetStatus(v string)

SetStatus sets the status-line message and marks the scene dirty. The Status field stays exported (the renderer reads it directly); this setter is the write path the view-model binds to, so a status change triggers a redraw on its own rather than relying on an adjacent SetItems/SetLoading to touch.

func (*Scene) SetSubs

func (s *Scene) SetSubs(subs []Subscription)

SetSubs replaces the sidebar subscriptions.

func (*Scene) SetTheme

func (s *Scene) SetTheme(t *toolkit.Theme)

SetTheme swaps the palette.

func (*Scene) SetThemeName

func (s *Scene) SetThemeName(name string)

SetThemeName records the theme choice (the host resolves it to a palette).

func (*Scene) SetThumb

func (s *Scene) SetThumb(id string, img *image.RGBA)

SetThumb attaches a decoded thumbnail for an item so the next Draw picks it up (the CardList re-renders the card from the live thumbnail cache each frame).

func (*Scene) SetUsenetServer

func (s *Scene) SetUsenetServer(addr string)

SetUsenetServer records the configured Usenet server address. A non-empty value gates the sidebar "Browse newsgroups" entry and titles the browse view.

func (*Scene) Settings

func (s *Scene) Settings() *settings.Settings

Settings snapshots the editor state for persistence.

func (*Scene) SidebarCollapsed

func (s *Scene) SidebarCollapsed() bool

SidebarCollapsed reports whether the sidebar is currently collapsed.

func (*Scene) SidebarContextAt

func (s *Scene) SidebarContextAt(x, y int) SidebarContext

SidebarContextAt maps a screen point to the sidebar context-menu target under it: a subscription, a folder, or nothing. The window layer calls it on a secondary click.

func (*Scene) SidebarFolders

func (s *Scene) SidebarFolders() []settings.Folder

SidebarFolders returns a copy of the current virtual-folder set (a stable snapshot for the settings persistence layer).

func (*Scene) SidebarSourceOpen

func (s *Scene) SidebarSourceOpen() source.Kind

SidebarSourceOpen reports which source group is currently expanded in the sidebar accordion, or "" when they are all collapsed (for tests / front-ends).

func (*Scene) SignInBrowser

func (s *Scene) SignInBrowser() string

SignInBrowser returns the browser launched for a provider sign-in flow (default|firefox|chrome|safari|edge).

func (*Scene) SubAt

func (s *Scene) SubAt(index int) (Subscription, bool)

SubAt returns the active-profile subscription at index, or ok=false when the index is out of range (e.g. AllFilter). The Handler uses it to name the source a sidebar right-click landed on.

func (*Scene) SubMarker

func (s *Scene) SubMarker(index int) (key string, marker int, ok bool)

SubMarker returns the subscription's key and current marker so the app can persist it as "seen" when the group is opened. ok is false for a bad index.

func (*Scene) SubscribeActive

func (s *Scene) SubscribeActive(k source.Kind, channel string) bool

SubscribeActive adds a source+channel subscription to the active profile (with the standard 25-item limit) and rebuilds the sidebar, reporting whether it was added (false when it was already present or there is no active profile). The app persists + re-aggregates after a true return.

func (*Scene) SyncBookmarkStar

func (s *Scene) SyncBookmarkStar()

SyncBookmarkStar sets the address-bar star to reflect whether the browser's current page is bookmarked (called after each navigation).

func (*Scene) TextZoomButtons

func (s *Scene) TextZoomButtons() (smaller, larger toolkit.Rect, shown bool)

TextZoomButtons returns the A−/A+ preview-toolbar pill rects and whether they are shown (an item is previewed and the pane is visible). Front-ends/tests call it after a Draw/HitTest.

func (*Scene) ThemeName

func (s *Scene) ThemeName() string

ThemeName returns the persisted theme choice ("system"|"light"|"dark").

func (*Scene) ToggleAccountBool

func (s *Scene) ToggleAccountBool(key string)

ToggleAccountBool flips a boolean credential field (e.g. Usenet TLS).

func (*Scene) ToggleBrowseNode

func (s *Scene) ToggleBrowseNode(name string)

ToggleBrowseNode expands or collapses the tree node with the given full name.

func (*Scene) ToggleGroup

func (s *Scene) ToggleGroup(base string)

ToggleGroup expands or collapses the Usenet post group with the given release base, then re-measures and re-lays-out its feed row so the CardList accounts for the members appearing or disappearing beneath the header. It is the action a click on the group card's disclosure chevron runs.

func (*Scene) TogglePreviewFocus

func (s *Scene) TogglePreviewFocus() bool

TogglePreviewFocus flips keyboard focus between the feed card list and the preview pane (the Tab key). Focus is only granted when the pane is focusable; otherwise it stays on the feed and the call is a no-op. It returns the new focused state.

func (*Scene) ToggleSidebar

func (s *Scene) ToggleSidebar()

ToggleSidebar collapses or expands the sidebar (transient UI chrome, not persisted).

func (*Scene) ToggleSidebarFolder

func (s *Scene) ToggleSidebarFolder(name string)

ToggleSidebarFolder flips a folder's collapse state (session-only). It is the action a click on a folder row runs.

func (*Scene) ToggleSidebarSource

func (s *Scene) ToggleSidebarSource(kind source.Kind)

ToggleSidebarSource drives the sidebar's source accordion: it expands kind's auto-group (collapsing whatever source was open), or collapses it if it was already the open one. Session-only, the action a click on a source header row runs; only one source group is ever open, keeping a huge subscription list short.

func (*Scene) TypeRune

func (s *Scene) TypeRune(r rune)

TypeRune appends r to whichever text field currently has focus: the topbar search (feed view) or the channel/rename/cache field (settings view).

func (*Scene) UnsubscribeActive

func (s *Scene) UnsubscribeActive(k source.Kind, channel string) bool

UnsubscribeActive removes the source+channel subscription from the active profile (case-insensitive on the channel) and rebuilds the sidebar, reporting whether one was removed (false when absent or there is no active profile). The app persists + re-aggregates after a true return.

func (*Scene) UsenetServer

func (s *Scene) UsenetServer() string

UsenetServer returns the configured Usenet server address ("" when none).

func (*Scene) WebPreviewActive

func (s *Scene) WebPreviewActive() bool

WebPreviewActive reports whether the embedded browser is the active preview content — a web-linked item is selected. Front-ends check it before routing a raw mouse/scroll/key event into the browser widget.

func (*Scene) WebPreviewURL

func (s *Scene) WebPreviewURL(it source.Item) string

WebPreviewURL returns the external http(s) target page to render for it, or "" when there is none or the source isn't web-renderable. Usenet posts preview as reconstructed images, not web pages, so they never return a URL here.

func (*Scene) ZoomBrowserIn

func (s *Scene) ZoomBrowserIn()

ZoomBrowserIn / ZoomBrowserOut zoom the embedded web preview through its MVVM commands — the same path the toolbar +/- buttons drive — so the zoom stays clamped and mirrors into the view-model.

func (*Scene) ZoomBrowserOut

func (s *Scene) ZoomBrowserOut()

func (*Scene) ZoomKeyDir

func (s *Scene) ZoomKeyDir(r rune) int

ZoomKeyDir reports the zoom direction bound to rune r: +1 for the zoom-in key, -1 for the zoom-out key, and 0 for anything else (including the zero rune). If the two keys are misconfigured to the same rune, zoom-in wins.

type SidebarContext

type SidebarContext struct {
	Kind    SidebarCtxKind
	Sub     int
	Source  source.Kind
	Channel string
	Folder  string
}

SidebarContext identifies what a sidebar right-click landed on, so the window layer can build the appropriate context menu. Kind is one of the exported SidebarCtx* constants; Sub/Source/Channel describe a subscription target and Folder names a folder target.

type SidebarCtxKind

type SidebarCtxKind int

SidebarCtxKind classifies a sidebar right-click target.

const (
	SidebarCtxNone   SidebarCtxKind = iota // empty space or a non-menuable entry
	SidebarCtxSub                          // a subscription row
	SidebarCtxFolder                       // a folder row
)

type Subscription

type Subscription struct {
	Source  source.Kind
	Channel string
	Label   string // display name; falls back to Channel or the source name
}

Subscription is one sidebar entry: a source + channel to pull, with a label.

type WebLink struct {
	Rect image.Rectangle
	Href string
}

WebLink is one clickable anchor in a rendered web preview: Rect is its bounds in the rendered image's own pixel coordinates (at the width it was rendered), Href the resolved absolute target. The pane maps a click through the image's display rect + scale back to these to find the anchor under the cursor.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL