ui

package
v0.0.0-...-4916209 Latest Latest
Warning

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

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package ui es el framework de interfaz de FlowCode: un árbol de elementos con layout de dos fases, eventos con foco y hit-testing, y una escena de primitivas que el renderer consume.

Por qué dos fases

Medir y pintar son operaciones distintas y se separan a propósito. Un elemento no sabe dónde va a acabar hasta que su padre ha repartido el espacio, así que primero se le pregunta cuánto ocupa (Layout), después se le dice dónde queda (Place) y solo entonces se le pide que emita sus primitivas (Paint). Mezclarlas obliga a pintar en dos pasadas o a asumir posiciones que todavía no existen, que es de donde salen las interfaces que "saltan" al redimensionar.

Por qué una escena y no llamadas a la GPU

Paint no dibuja: añade primitivas a una Scene, una lista plana de rectángulos y textos. La ventaja es doble. La interfaz entera se puede probar sin abrir una ventana ni tener tarjeta gráfica —basta comparar la escena resultante—, y el renderer recibe todo el fotograma junto, que es lo que le permite agruparlo en unos pocos envíos en lugar de uno por widget.

Qué no hay aquí

No hay estado global, ni un bucle de eventos, ni nada específico del sistema operativo: ui no importa platform. Los eventos de esta capa son propios y la capa app traduce los del sistema. Esa frontera es lo que permite probar un widget entero pasándole eventos sintéticos desde un test.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Align

type Align uint8

Align sitúa un elemento dentro de un hueco mayor que él.

const (
	// Start pega al principio: izquierda en horizontal, arriba en vertical.
	Start Align = iota
	Center
	End

	// Stretch estira el elemento hasta llenar el eje transversal.
	Stretch
)

Alineaciones posibles.

type Animator

type Animator interface {
	Element

	// Tick avanza la animación y devuelve si queda algo por animar.
	Tick(dt time.Duration) bool
}

Animator es un elemento cuyo aspecto cambia con el tiempo sin que llegue ningún evento.

Es la excepción deliberada al damage tracking: mientras Tick devuelva true, la ventana se repinta. Por eso devuelve un booleano en lugar de animar siempre — una animación que no sabe terminar convierte el editor en un bucle a 120 fps quemando batería, que es justo lo que el proyecto evita.

type Axis

type Axis uint8

Axis distingue las dos direcciones de disposición.

const (
	Horizontal Axis = iota
	Vertical
)

Direcciones posibles.

func (Axis) Cross

func (a Axis) Cross(s geom.Size) float32

Cross devuelve la componente en el eje transversal.

func (Axis) Main

func (a Axis) Main(s geom.Size) float32

Main devuelve la componente de un tamaño en el eje principal.

func (Axis) Size

func (a Axis) Size(main, cross float32) geom.Size

Size compone un tamaño a partir de sus componentes principal y transversal.

type Base

type Base struct {
	// contains filtered or unexported fields
}

Base aporta la parte mecánica de Element: recordar dónde está y no reaccionar a nada. Un widget la incrusta y se limita a implementar Layout y Paint.

func (*Base) Bounds

func (b *Base) Bounds() geom.Rect

Bounds devuelve el rectángulo asignado.

func (*Base) Event

func (b *Base) Event(Event) Handled

Event ignora todo. Un widget que reaccione a la entrada redefine este método.

func (*Base) Place

func (b *Base) Place(r geom.Rect)

Place fija el rectángulo del elemento.

type Box

type Box struct {
	Base

	Child Element

	Background  geom.Color
	Border      geom.Color
	BorderSides Sides
	BorderWidth float32

	Padding Insets

	// Clip recorta al hijo al rectángulo de la caja. Cuesta un recorte más en
	// el renderer, así que solo se activa donde el contenido puede desbordar.
	Clip bool

	// MinSize fuerza un tamaño mínimo, para las cajas sin hijo que solo pintan
	// fondo, como las barras del chasis.
	MinSize geom.Size
	// contains filtered or unexported fields
}

Box decora a un hijo con fondo, borde y márgenes interiores.

Está unificado en un solo elemento en lugar de repartido en tres decoradores encadenados porque en una interfaz los tres van casi siempre juntos, y cada nivel de anidamiento es un elemento más que recorrer en el hit-testing y una indirección más al pintar.

func NewBox

func NewBox(child Element) *Box

NewBox envuelve a un hijo, que puede ser nil para una caja que solo pinta.

func (*Box) Children

func (b *Box) Children() []Element

Children implementa Container.

func (*Box) Layout

func (b *Box) Layout(c Constraints) geom.Size

Layout mide al hijo dentro de los márgenes.

func (*Box) Paint

func (b *Box) Paint(s *Scene)

Paint pinta fondo, hijo y borde, en ese orden: el borde va encima para que un hijo que desborde no se lo coma.

func (*Box) Place

func (b *Box) Place(r geom.Rect)

Place sitúa al hijo dentro de los márgenes.

func (*Box) WithBackground

func (b *Box) WithBackground(c geom.Color) *Box

WithBackground fija el color de fondo.

func (*Box) WithBorder

func (b *Box) WithBorder(c geom.Color, sides Sides) *Box

WithBorder fija el color y los lados del borde.

func (*Box) WithPadding

func (b *Box) WithPadding(in Insets) *Box

WithPadding fija los márgenes interiores.

type CharEvent

type CharEvent struct {
	Rune rune
	Mods Mods
}

CharEvent es un carácter ya resuelto por el layout del teclado. Es la única fuente de verdad para insertar texto: deducirlo de las teclas físicas rompe en cuanto el usuario escribe en un teclado que no es el del programador.

type Child

type Child struct {
	Element Element
	Flex    float32
}

Child es un hijo de un contenedor junto con su factor de crecimiento.

Flex a cero significa "ocupa lo que necesites"; cualquier valor mayor reparte el espacio sobrante en proporción. Dos hijos con flex 1 y 2 se llevan un tercio y dos tercios de lo que quede después de medir a los rígidos.

type Constraints

type Constraints struct {
	Min, Max geom.Size
}

Constraints es el espacio disponible para un elemento: un mínimo que debe ocupar y un máximo que no puede rebasar.

El modelo es el de Flutter y el de Zed: las restricciones bajan por el árbol, los tamaños suben. Un elemento no consulta a su padre ni mira la ventana; solo conoce lo que le han dado.

func Loose

func Loose(s geom.Size) Constraints

Loose construye restricciones con un máximo y sin mínimo: el elemento puede ocupar lo que quiera hasta ese límite.

func Tight

func Tight(s geom.Size) Constraints

Tight construye restricciones que solo admiten un tamaño exacto.

func Unbounded

func Unbounded() Constraints

Unbounded construye restricciones sin límite, para medir el contenido de algo que va a desplazarse: una lista dentro de un scroll puede ser más alta que la ventana, y preguntarle su tamaño natural es justo lo que hace falta para saber cuánto se puede desplazar.

func (Constraints) Constrain

func (c Constraints) Constrain(s geom.Size) geom.Size

Constrain ajusta un tamaño a las restricciones.

func (Constraints) Deflate

func (c Constraints) Deflate(in Insets) Constraints

Deflate reduce el espacio disponible por unos márgenes, sin bajar de cero.

func (Constraints) Loosen

func (c Constraints) Loosen() Constraints

Loosen quita el mínimo conservando el máximo. Es lo que un contenedor pasa a un hijo al que no quiere obligar a llenar el hueco.

type Container

type Container interface {
	Children() []Element
}

Container es un elemento con hijos. Lo implementan los que reparten espacio, y es lo que permite al Root recorrer el árbol para el hit-testing, el foco y las animaciones.

Los hijos se devuelven en orden de pintado: el último es el que queda encima, y por tanto el primero en recibir un clic.

type Dialog

type Dialog struct {
	Base

	// Message es la pregunta y Detail la consecuencia de contestarla mal.
	Message string
	Detail  string

	// Buttons son las opciones, de izquierda a derecha. Cancel es el índice de
	// la que corresponde a no hacer nada, la que ejecutan Escape y el clic fuera.
	Buttons []string
	Cancel  int

	// Selected es la opción marcada. OnChoose recibe el índice elegido.
	Selected int
	OnChoose func(i int)
	// contains filtered or unexported fields
}

Dialog es una pregunta con botones: un mensaje, una explicación debajo y una fila de opciones.

Es el único sitio de la interfaz donde el programa se detiene a esperar una decisión, y por eso está hecho para que decidir sea rápido: hay un botón marcado de antemano, Intro lo acepta y Escape cancela. Quien quiera pensarlo tiene el ratón; quien no, no tiene que soltar el teclado.

Va dentro de un Overlay, que es quien pone el velo y lo centra.

func NewDialog

func NewDialog(th *Theme, buttons ...string) *Dialog

NewDialog crea un diálogo con sus opciones. La última suele ser la de cancelar, y es la que se marca como tal por omisión.

func (*Dialog) AcceptsFocus

func (d *Dialog) AcceptsFocus() bool

AcceptsFocus implementa Focusable: el diálogo se queda el teclado mientras está abierto.

func (*Dialog) Event

func (d *Dialog) Event(e Event) Handled

Event atiende el teclado y el ratón.

func (*Dialog) Layout

func (d *Dialog) Layout(c Constraints) geom.Size

Layout mide el diálogo: el alto lo deciden el texto y la fila de botones, y el ancho lo impone el panel que lo contiene.

func (*Dialog) Paint

func (d *Dialog) Paint(s *Scene)

Paint dibuja el texto y los botones.

func (*Dialog) Place

func (d *Dialog) Place(r geom.Rect)

Place reparte el rectángulo y calcula dónde cae cada botón.

Los botones se colocan de derecha a izquierda porque es el borde contra el que se alinean: así el último de la lista queda pegado al margen y añadir uno más no mueve a los demás.

func (*Dialog) SetFocus

func (d *Dialog) SetFocus(bool)

SetFocus implementa Focusable.

func (*Dialog) SetHover

func (d *Dialog) SetHover(h bool)

SetHover implementa Hoverable.

type Element

type Element interface {
	// Layout mide el elemento dentro de las restricciones dadas y devuelve el
	// tamaño que quiere ocupar. El resultado debe respetar c: devolver algo
	// mayor que c.Max es un error del elemento, no una petición al padre.
	Layout(c Constraints) geom.Size

	// Place fija el rectángulo definitivo, en coordenadas de la ventana. El
	// padre lo llama después de Layout y puede dar un rectángulo distinto del
	// tamaño pedido: quien reparte el espacio es siempre el padre.
	Place(r geom.Rect)

	// Bounds devuelve el rectángulo asignado en el último Place. Es lo que usan
	// el hit-testing y el recorte.
	Bounds() geom.Rect

	// Paint emite las primitivas del elemento en la escena.
	Paint(s *Scene)

	// Event reacciona a un evento ya dirigido a este elemento. Devolver
	// Consumed detiene la propagación hacia los ancestros.
	Event(e Event) Handled
}

Element es cualquier cosa que ocupa sitio en la interfaz.

El ciclo completo de un fotograma es Layout → Place → Paint. Event ocurre entre fotogramas, sobre las posiciones que dejó el último Place.

type Event

type Event interface {
	// contains filtered or unexported methods
}

Event es cualquier entrada dirigida a un elemento. El conjunto es cerrado: un type switch sobre los tipos de este archivo los cubre todos.

Por qué no se reutilizan los de platform

La capa ui no puede importar platform: está por encima en el apilamiento y la prueba de arquitectura lo impide. No es burocracia. Los eventos del sistema hablan de teclas físicas y de coordenadas de ventana; los de la interfaz hablan de intenciones ya resueltas —"la rueda se movió sobre este widget"— y llegan con el foco y el hit-testing aplicados. Que sean tipos distintos es lo que permite construir un widget y probarlo entero sin abrir una ventana.

type Flow

type Flow struct {
	Base

	Axis Axis

	// Gap es la separación entre hijos consecutivos.
	Gap float32

	// Cross alinea a los hijos en el eje transversal. Stretch es lo habitual en
	// una interfaz: una fila de una columna ocupa todo el ancho.
	Cross Align
	// contains filtered or unexported fields
}

Flow dispone a sus hijos en fila o en columna.

Es el único contenedor que reparte espacio, y con él se construye todo lo demás: el chasis del IDE es una columna con una fila dentro. El algoritmo es el de flexbox reducido a lo que un editor necesita —medir a los rígidos, repartir el resto entre los flexibles— sin envolver líneas ni ordenar.

func Column

func Column(children ...Element) *Flow

Column crea un flujo vertical con los hijos dados, todos rígidos.

func Row

func Row(children ...Element) *Flow

Row crea un flujo horizontal con los hijos dados, todos rígidos.

func (*Flow) Add

func (f *Flow) Add(e Element, flex float32) *Flow

Add añade un hijo con su factor de crecimiento y devuelve el propio flujo, para poder encadenar la construcción del árbol.

func (*Flow) Children

func (f *Flow) Children() []Element

Children implementa Container.

func (*Flow) Layout

func (f *Flow) Layout(c Constraints) geom.Size

Layout mide a los hijos en dos pasadas: primero los rígidos, que dicen cuánto necesitan, y después los flexibles, que se reparten lo que sobró.

El orden importa. Medir primero a los flexibles obligaría a adivinar cuánto van a dejar libre, que es exactamente el problema que el reparto en dos pasadas resuelve.

func (*Flow) Paint

func (f *Flow) Paint(s *Scene)

Paint pinta a los hijos en orden. El último queda encima, que es el mismo orden en el que el hit-testing los consulta al revés.

func (*Flow) Place

func (f *Flow) Place(r geom.Rect)

Place reparte el rectángulo entre los hijos según lo medido.

func (*Flow) WithCross

func (f *Flow) WithCross(a Align) *Flow

WithCross fija la alineación transversal.

func (*Flow) WithGap

func (f *Flow) WithGap(gap float32) *Flow

WithGap fija la separación entre hijos.

type FocusEvent

type FocusEvent struct {
	Focused bool
}

FocusEvent avisa a un elemento de que ha ganado o perdido el teclado. Lo emite el Root además de llamar a SetFocus, para los elementos compuestos que necesitan reaccionar y no solo recordar el estado.

type Focusable

type Focusable interface {
	Element

	// AcceptsFocus permite a un elemento retirarse temporalmente del ciclo del
	// tabulador —un campo deshabilitado, un panel oculto— sin salir del árbol.
	AcceptsFocus() bool

	// SetFocus notifica la entrada o salida del foco. El elemento guarda el
	// estado; el Root es el único que decide quién lo tiene.
	SetFocus(focused bool)
}

Focusable es un elemento que puede recibir el teclado.

type Fonts

type Fonts struct {
	Painter *text.Painter

	// UI es la variante de la interfaz; Code la del editor. Un panel de
	// búsqueda o el árbol de archivos usan UI; una vista previa de código, Code.
	UI   text.FaceID
	Code text.FaceID
}

Fonts son las variantes tipográficas que usa la interfaz, con el pintor que las mide y las dibuja.

Van dentro del tema porque el tamaño del texto es una decisión de tema, y porque medir es una operación de disposición: un widget necesita el ancho de una cadena en Layout, mucho antes de que exista una escena.

func (Fonts) Advance

func (f Fonts) Advance(id text.FaceID) float32

Advance es el avance de un carácter. En monoespaciado vale para todos.

func (Fonts) IndexAt

func (f Fonts) IndexAt(id text.FaceID, s string, x float32) int

IndexAt traduce una coordenada horizontal en un índice de byte dentro de la cadena. Es lo que convierte un clic en una posición de cursor.

func (Fonts) LineHeight

func (f Fonts) LineHeight(id text.FaceID) float32

LineHeight es la separación entre líneas de una variante.

func (Fonts) Measure

func (f Fonts) Measure(id text.FaceID, s string) float32

Measure devuelve el ancho de una cadena en píxeles lógicos.

func (Fonts) PositionOf

func (f Fonts) PositionOf(id text.FaceID, s string, index int) float32

PositionOf es la operación inversa de IndexAt.

type Handled

type Handled bool

Handled indica si un evento se consumió.

const (
	// Ignored deja que el evento siga subiendo hacia los ancestros.
	Ignored Handled = false

	// Consumed detiene la propagación.
	Consumed Handled = true
)

Resultados posibles de Event.

type Hoverable

type Hoverable interface {
	Element
	SetHover(hovered bool)
}

Hoverable es un elemento que cambia de aspecto al pasar el puntero por encima. El Root mantiene el estado para que un widget no tenga que rastrear las entradas y salidas del ratón por su cuenta, que es donde se acumulan los resaltados que se quedan pegados.

type Input

type Input struct {
	Base

	Theme       *Theme
	Face        text.FaceID
	Text        string
	Placeholder string

	OnChange func(s string)
	OnSubmit func(s string)
	OnCancel func()
	// contains filtered or unexported fields
}

Input es un campo de texto de una sola línea: la caja de búsqueda, el buscador difuso de archivos, la paleta de comandos.

No es un editor. Un editor necesita el modelo de texto con transacciones, deshacer y múltiples cursores del Hito 3, y meterlo aquí sería construirlo dos veces. Este widget guarda una cadena y un cursor, que es exactamente lo que hace falta para escribir un nombre de archivo.

func NewInput

func NewInput(th *Theme) *Input

NewInput crea un campo con el tema dado.

func (*Input) AcceptsFocus

func (in *Input) AcceptsFocus() bool

AcceptsFocus implementa Focusable.

func (*Input) Cursor

func (in *Input) Cursor() int

Cursor devuelve la posición del cursor en bytes, para las pruebas y para los widgets que se construyan encima.

func (*Input) Event

func (in *Input) Event(e Event) Handled

Event atiende a la escritura, a la navegación y al ratón.

func (*Input) Focused

func (in *Input) Focused() bool

Focused indica si el campo tiene el teclado.

func (*Input) Layout

func (in *Input) Layout(c Constraints) geom.Size

Layout ocupa el ancho disponible y el alto de una fila cómoda.

func (*Input) Paint

func (in *Input) Paint(s *Scene)

Paint dibuja fondo, texto, selección y cursor.

func (*Input) SetFocus

func (in *Input) SetFocus(f bool)

SetFocus implementa Focusable.

func (*Input) SetText

func (in *Input) SetText(s string)

SetText sustituye el contenido y coloca el cursor al final.

type Insets

type Insets struct {
	Top, Right, Bottom, Left float32
}

Insets son márgenes por lado, en píxeles lógicos.

func All

func All(v float32) Insets

All construye márgenes iguales en los cuatro lados.

func Sym

func Sym(h, v float32) Insets

Sym construye márgenes simétricos: h a los lados, v arriba y abajo.

func (Insets) Apply

func (i Insets) Apply(r geom.Rect) geom.Rect

Apply encoge un rectángulo por los márgenes.

func (Insets) Height

func (i Insets) Height() float32

Height es el margen vertical total.

func (Insets) Width

func (i Insets) Width() float32

Width es el margen horizontal total.

type Key

type Key uint8

Key es una tecla de control.

El conjunto es corto a propósito, y no una copia de la tabla completa de platform. Un widget solo reacciona a navegación, edición y confirmación; cualquier otra combinación es un comando de la aplicación y su sitio es el registro de comandos con cláusulas `when` del Hito 3, no un `switch` dentro de una lista. Mantener la tabla corta impide que los atajos se vayan repartiendo por los widgets, que es como se llega a no saber qué hace F5.

const (
	KeyNone Key = iota
	KeyLeft
	KeyRight
	KeyUp
	KeyDown
	KeyHome
	KeyEnd
	KeyPageUp
	KeyPageDown
	KeyEnter
	KeyEscape
	KeyTab
	KeyBackspace
	KeyDelete
	KeySpace
)

Teclas reconocidas por los widgets.

func (Key) String

func (k Key) String() string

String devuelve el nombre de la tecla, para diagnósticos y para los volcados de escena.

type KeyEvent

type KeyEvent struct {
	Key    Key
	Mods   Mods
	Repeat bool
}

KeyEvent es una tecla de control: navegación, edición o confirmación. El texto no llega por aquí sino por CharEvent.

type Label

type Label struct {
	Base

	Fonts Fonts
	Face  text.FaceID
	Text  string
	Color geom.Color

	// Align coloca el texto en el eje horizontal cuando le sobra sitio.
	Align Align

	// Truncate recorta con puntos suspensivos en lugar de desbordar. Se usa en
	// las rutas del panel lateral, donde el ancho lo decide el usuario
	// arrastrando y el texto no cabe casi nunca.
	Truncate bool
	// contains filtered or unexported fields
}

Label es una línea de texto. Es el widget más simple que existe y aun así carga con dos decisiones que se repiten en toda la interfaz: cómo se mide una cadena y dónde queda su línea base dentro de una fila más alta que ella.

func NewLabel

func NewLabel(f Fonts, face text.FaceID, s string, c geom.Color) *Label

NewLabel crea una etiqueta con el tema y la variante dados.

func (*Label) Layout

func (l *Label) Layout(c Constraints) geom.Size

Layout mide la cadena y reserva una línea de alto.

func (*Label) Paint

func (l *Label) Paint(s *Scene)

Paint emite el texto centrado verticalmente en su rectángulo.

func (*Label) SetText

func (l *Label) SetText(s string)

SetText cambia el texto.

type List

type List struct {
	Base

	Theme *Theme
	Model ListModel
	Face  text.FaceID

	// RowHeight a cero usa la del tema.
	RowHeight float32

	// MaxRows, si es mayor que cero, hace que la lista pida el alto de sus
	// filas hasta ese máximo en lugar de llenar el hueco que le den.
	//
	// Es lo que necesita un panel emergente: la paleta de comandos con tres
	// resultados debe medir tres filas, no dejar media pantalla vacía por
	// debajo. Una lista dentro de un panel fijo no lo usa, porque ahí el hueco
	// existe siempre.
	MaxRows int

	// Selected es la fila seleccionada, o -1 si no hay ninguna.
	Selected int

	OnSelect   func(i int)
	OnActivate func(i int)

	// ActivateOnSingleClick decide cuándo se activa una fila con el ratón. Un
	// árbol de archivos despliega con un solo clic; una lista de resultados
	// espera el doble. La diferencia es si activar es reversible: desplegar una
	// carpeta lo es, abrir un archivo no tanto.
	ActivateOnSingleClick bool
	// contains filtered or unexported fields
}

List es una lista vertical virtualizada con selección, teclado e inercia.

Solo pinta las filas que caben en pantalla. Es la misma propiedad que hace que abrir un archivo de un gigabyte sea instantáneo, aplicada a la interfaz: el coste de un fotograma depende de la altura de la ventana, nunca de cuántos elementos haya.

func NewList

func NewList(th *Theme, m ListModel) *List

NewList crea una lista sobre un modelo.

func (*List) AcceptsFocus

func (l *List) AcceptsFocus() bool

AcceptsFocus implementa Focusable.

func (*List) Event

func (l *List) Event(e Event) Handled

Event atiende al ratón, a la rueda y al teclado de navegación.

func (*List) Focused

func (l *List) Focused() bool

Focused indica si la lista tiene el teclado.

func (*List) Layout

func (l *List) Layout(c Constraints) geom.Size

Layout ocupa todo el hueco disponible. Una lista no pide un tamaño: se le da uno y ella decide cuántas filas caben.

func (*List) Paint

func (l *List) Paint(s *Scene)

Paint dibuja únicamente las filas visibles.

func (*List) Place

func (l *List) Place(r geom.Rect)

Place fija el rectángulo y recalcula cuánto se puede desplazar.

func (*List) Reveal

func (l *List) Reveal(i int)

Reveal desplaza lo justo para que una fila quede visible.

func (*List) Scroller

func (l *List) Scroller() *Scroller

Scroller da acceso al desplazamiento, para llevarlo desde fuera.

func (*List) Select

func (l *List) Select(i int)

Select mueve la selección y la trae a la vista.

func (*List) SetFocus

func (l *List) SetFocus(f bool)

SetFocus implementa Focusable. La selección cambia de color al perder el foco: sin esa distinción, dos paneles abiertos parecen tener el teclado los dos a la vez.

func (*List) SetHover

func (l *List) SetHover(h bool)

SetHover implementa Hoverable: al salir el puntero se apaga la fila resaltada.

func (*List) Tick

func (l *List) Tick(dt time.Duration) bool

Tick implementa Animator para la inercia de la rueda.

type ListModel

type ListModel interface {
	// Count es el número de filas.
	Count() int

	// Row devuelve el contenido de una fila. Se llama solo para las visibles y
	// una vez por fotograma, así que puede formatear, pero no debe leer del
	// disco ni bloquear.
	Row(i int) ListRow
}

ListModel describe el contenido de una lista sin materializarlo.

type ListRow

type ListRow struct {
	Text string

	// Detail va alineado a la derecha y atenuado: el número de coincidencias,
	// el atajo de un comando, el estado de git de un archivo.
	Detail string

	// Indent son niveles de sangría, para las listas que representan árboles.
	Indent int

	// Marker es lo que va antes del texto: el triángulo de una carpeta, un
	// punto de modificado. Vacío deja el hueco sin dibujar nada.
	Marker string

	// Color sustituye al color de texto del tema cuando no es el cero.
	Color geom.Color

	// Dim pinta la fila atenuada: archivos ignorados, entradas deshabilitadas.
	Dim bool
}

ListRow es el contenido de una fila de lista, ya resuelto.

Es una estructura de datos y no un elemento a propósito. Una lista virtualizada no puede materializar sus filas: el árbol de archivos de un proyecto grande tiene decenas de miles de entradas y construir un widget por cada una gastaría más memoria en la interfaz que en el contenido. El modelo describe la fila; la lista la pinta si se ve.

type Metrics

type Metrics struct {
	ActivityBar float32 // ancho de la barra de iconos
	Sidebar     float32 // ancho del panel lateral
	TabBar      float32 // alto de la barra de pestañas
	StatusBar   float32 // alto de la barra inferior
	Row         float32 // alto de una fila de lista o de árbol
	Padding     float32 // margen interior estándar
	Indent      float32 // sangría por nivel en el árbol
	Scrollbar   float32
	Border      float32
}

Metrics son las medidas del chasis en píxeles lógicos.

type Mods

type Mods uint8

Mods es el conjunto de modificadores activos.

const (
	ModShift Mods = 1 << iota
	ModCtrl
	ModAlt
	ModSuper
)

Modificadores. Super es la tecla Windows o Command.

func (Mods) Has

func (m Mods) Has(x Mods) bool

Has indica si todos los modificadores dados están activos.

type MouseButton

type MouseButton uint8

MouseButton identifica un botón.

const (
	ButtonLeft MouseButton = iota
	ButtonRight
	ButtonMiddle
	ButtonBack
	ButtonForward
)

Botones reconocidos por la interfaz. Los laterales no se usan todavía, pero el editor los necesitará para navegar hacia atrás y hacia delante.

type MouseEvent

type MouseEvent struct {
	Kind   MouseKind
	Pos    geom.Point
	Button MouseButton
	Mods   Mods

	// Clicks es 1 para una pulsación simple, 2 para la segunda de un doble clic
	// y así sucesivamente. Solo tiene sentido en MouseDown.
	Clicks int
}

MouseEvent es cualquier actividad del puntero. Pos viene en coordenadas de la ventana, no relativas al elemento: comparar con Bounds es directo y no hay que arrastrar una traslación por el árbol.

type MouseKind

type MouseKind uint8

MouseKind distingue las tres cosas que puede hacer el puntero.

const (
	MouseMove MouseKind = iota
	MouseDown
	MouseUp

	// MouseLeave se entrega al elemento que deja de estar bajo el puntero,
	// incluido el caso de que el puntero salga de la ventana.
	MouseLeave
)

Tipos de actividad del puntero.

type Overlay

type Overlay struct {
	Base

	Theme   *Theme
	Child   Element
	Visible bool

	// Width es el ancho del panel; a cero usa uno cómodo para leer una lista de
	// resultados. Top es lo que baja desde el borde superior.
	Width float32
	Top   float32

	// OnDismiss se llama al pulsar Escape o fuera del panel.
	OnDismiss func()
	// contains filtered or unexported fields
}

Overlay es un panel flotante sobre el resto de la interfaz: la paleta de comandos, el buscador de archivos, un diálogo de confirmación.

Va dentro de un Stack, compartiendo rectángulo con el contenido en lugar de robarle sitio. Mientras está oculto no ocupa nada —su rectángulo es vacío—, de modo que ni recibe clics ni entra en el ciclo del tabulador: un panel cerrado que sigue interceptando el teclado es de los errores más difíciles de diagnosticar, porque no se ve.

func NewOverlay

func NewOverlay(th *Theme, child Element) *Overlay

NewOverlay crea un panel flotante, inicialmente oculto.

func (*Overlay) Children

func (o *Overlay) Children() []Element

Children implementa Container. Con el panel oculto no se declaran hijos: así ni el hit-testing ni el foco pueden alcanzarlos.

func (*Overlay) Event

func (o *Overlay) Event(e Event) Handled

Event cierra el panel con Escape o pulsando fuera de él.

Llega aquí solo lo que el hijo no ha querido, porque los eventos suben desde el elemento más profundo: el campo de texto se queda con las teclas que le interesan y el panel se ocupa del resto.

func (*Overlay) Hide

func (o *Overlay) Hide()

Hide cierra el panel.

func (*Overlay) Layout

func (o *Overlay) Layout(c Constraints) geom.Size

Layout ocupa todo el hueco disponible: el velo cubre la ventana entera.

func (*Overlay) Paint

func (o *Overlay) Paint(s *Scene)

Paint dibuja el velo, la sombra y el panel.

func (*Overlay) Place

func (o *Overlay) Place(r geom.Rect)

Place centra el panel horizontalmente dentro del área.

func (*Overlay) Show

func (o *Overlay) Show() Element

Show abre el panel. Devuelve el hijo para poder darle el foco.

type Palette

type Palette struct {
	// Superficies, de la más honda a la más elevada.
	Background geom.Color // el fondo del editor
	Panel      geom.Color // paneles laterales
	PanelDeep  geom.Color // barra de actividad, lo más al fondo
	Bar        geom.Color // barras de pestañas y de estado
	Border     geom.Color

	// Texto.
	Text   geom.Color
	Dim    geom.Color // secundario: rutas, contadores, marcadores
	Bright geom.Color // lo que hay que mirar: la pestaña activa

	// Énfasis e interacción.
	Accent    geom.Color
	Hover     geom.Color
	Active    geom.Color // fila seleccionada con el foco puesto
	Inactive  geom.Color // fila seleccionada sin foco
	Selection geom.Color // texto seleccionado
	Cursor    geom.Color

	// Elementos flotantes.
	Scrim   geom.Color // oscurece el fondo bajo un panel emergente
	Overlay geom.Color
	Shadow  geom.Color

	// Barras de desplazamiento.
	Thumb      geom.Color
	ThumbHover geom.Color

	// Estados.
	Error   geom.Color
	Warning geom.Color
	Ok      geom.Color

	// Sintaxis. Son ocho y no cuarenta a propósito: un tema con un color por
	// cada construcción del lenguaje no se lee mejor, se lee peor, porque el
	// ojo deja de distinguir qué es importante. Estos son los que cualquier
	// tema de cualquier editor separa.
	//
	// Los nombres están aquí, en la capa de interfaz, aunque los tipos de tramo
	// los defina la capa de sintaxis, que está por encima. Es deliberado: la
	// regla del proyecto es que todo el color viva en este archivo, y la
	// correspondencia entre un tipo y su color son ocho líneas en el editor.
	SynKeyword  geom.Color
	SynType     geom.Color
	SynConstant geom.Color
	SynString   geom.Color
	SynComment  geom.Color
	SynFunction geom.Color
	SynOperator geom.Color
	SynPunct    geom.Color

	// Bracket es el fondo del delimitador emparejado con el que hay junto al
	// cursor.
	Bracket geom.Color
}

Palette son los colores del tema.

Los nombres describen la función, no el tono: "panel", no "gris oscuro". Es lo que permite que exista un tema claro sin que los widgets mientan.

type Prim

type Prim struct {
	Kind PrimKind

	// Rect es el rectángulo a rellenar. En una primitiva de texto solo Min es
	// significativo: es la esquina superior izquierda de la caja de línea, y el
	// ancho depende de la fuente, que aquí no se consulta.
	Rect geom.Rect

	// Clip es el recorte vigente cuando se emitió.
	Clip geom.Rect

	Color geom.Color

	// Text y Face solo se usan en PrimText.
	Text string
	Face text.FaceID
}

Prim es una primitiva de dibujo ya resuelta: sabe dónde va, de qué color y con qué recorte.

type PrimKind

type PrimKind uint8

PrimKind distingue los dos tipos de primitiva. Son dos y no más porque abajo solo hay uno: todo acaba siendo un cuadrilátero texturizado. Un rectángulo usa el texel opaco del atlas y un texto usa sus glifos, pero para la GPU son la misma llamada.

const (
	PrimRect PrimKind = iota
	PrimText
)

Tipos de primitiva.

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

Renderer convierte una escena en llamadas a la GPU.

Es la única pieza de ui que habla con gpu, y es deliberadamente delgada: todo lo que hace es recorrer las primitivas en orden y acumularlas en un buffer de cuadriláteros que se envía cuando cambia el recorte.

Que baste con eso es consecuencia de una decisión tomada dos capas más abajo: los rectángulos usan el texel opaco del atlas de glifos, así que fondos, bordes, cursores y texto comparten textura. Una pantalla entera del editor —chasis, panel lateral, pestañas y código— cabe en tantos envíos como recortes distintos haya, que son unos pocos, y no en uno por widget.

func NewRenderer

func NewRenderer(a *text.Atlas, p *text.Painter) *Renderer

NewRenderer crea un renderer sobre un atlas y su pintor.

func (*Renderer) Render

func (r *Renderer) Render(f gpu.Frame, s *Scene)

Render dibuja la escena en el fotograma.

type Root

type Root struct {
	Child Element
	Theme *Theme
	// contains filtered or unexported fields
}

Root es la raíz del árbol de interfaz: dispone, pinta y reparte los eventos.

Concentra aquí las tres cosas que no puede resolver un widget por sí solo —quién tiene el foco, quién está bajo el puntero y quién capturó el ratón— porque son propiedades del árbol entero. Repartirlas entre los widgets es como se llega a dos elementos que se creen los dos enfocados, o a un resaltado que se queda encendido porque nadie le avisó de que el puntero se fue.

func NewRoot

func NewRoot(th *Theme, child Element) *Root

NewRoot crea la raíz sobre un árbol ya construido.

func (*Root) Animate

func (r *Root) Animate(now clock.Time) bool

Animate avanza las animaciones y devuelve si hay que seguir repintando.

Es la única puerta por la que la interfaz puede pedir fotogramas sin que haya ocurrido nada, y por eso devuelve un booleano en vez de programar un temporizador: quien manda sobre el ciclo de dibujo es la capa de arriba.

func (*Root) Bounds

func (r *Root) Bounds() geom.Rect

Bounds es el área de la raíz.

func (*Root) Event

func (r *Root) Event(e Event) Handled

Event reparte un evento y devuelve si alguien lo consumió.

func (*Root) Focus

func (r *Root) Focus(f Focusable)

Focus traslada el foco, avisando al que lo pierde y al que lo gana.

func (*Root) FocusNext

func (r *Root) FocusNext(back bool)

FocusNext mueve el foco al siguiente enfocable en orden de árbol, o al anterior si back es cierto. El ciclo da la vuelta al llegar al final.

func (*Root) Focused

func (r *Root) Focused() Focusable

Focused devuelve quién tiene el foco.

func (*Root) Layout

func (r *Root) Layout(size geom.Size)

Layout mide y sitúa el árbol para un tamaño de ventana.

func (*Root) Paint

func (r *Root) Paint() *Scene

Paint construye la escena del fotograma. La escena se reutiliza entre fotogramas y solo es válida hasta el siguiente Paint.

func (*Root) Scene

func (r *Root) Scene() *Scene

Scene devuelve la última escena construida.

type Scene

type Scene struct {
	// contains filtered or unexported fields
}

Scene es la lista plana de primitivas de un fotograma.

Es la frontera entre la interfaz y la GPU, y existe por dos razones. La primera es la comprobabilidad: una escena es una descripción exacta de lo que se habría pintado, así que un test compara escenas y no píxeles, sin ventana ni tarjeta gráfica. La segunda es el agrupamiento: el renderer recibe el fotograma entero de golpe y puede meter en un solo envío todo lo que comparte recorte, en lugar de un envío por widget.

func (*Scene) Clip

func (s *Scene) Clip() geom.Rect

Clip devuelve el recorte vigente.

func (*Scene) Dump

func (s *Scene) Dump() string

Dump devuelve una descripción textual y determinista de la escena, una primitiva por línea.

Es la base de las pruebas de instantánea: comparar dos cadenas dice exactamente qué cambió y dónde, mientras que comparar estructuras solo dice que algo cambió. Los índices y las coordenadas se escriben en la forma más corta que las representa, para que un cambio de un píxel no se esconda entre ceros.

func (*Scene) Fill

func (s *Scene) Fill(r geom.Rect, c geom.Color)

Fill emite un rectángulo liso.

Los rectángulos vacíos, transparentes o totalmente fuera de recorte se descartan aquí. Podría hacerlo el renderer, pero descartarlos en el origen mantiene las escenas de los tests libres de ruido invisible: lo que aparece en un volcado es exactamente lo que se ve.

func (*Scene) Number

func (s *Scene) Number(n int) string

Number formatea un entero en una cadena válida hasta el siguiente Reset.

Existe por el camino más caliente de todo el editor: los números de línea del margen, que se formatean para cada línea visible en cada fotograma. strconv.Itoa asignaría una cadena por línea y por fotograma —decenas de miles de asignaciones por segundo al desplazarse— y esas pausas del recolector se ven como tirones.

La arena crece por bloques que nunca se reasignan mientras están en uso: si se ampliara un bloque ya entregado, las cadenas devueltas antes apuntarían a memoria liberada. Por eso cuando un bloque se llena se pasa al siguiente en lugar de hacerlo crecer.

func (*Scene) Pop

func (s *Scene) Pop()

Pop deshace el último Push.

func (*Scene) Prims

func (s *Scene) Prims() []Prim

Prims devuelve las primitivas emitidas, en orden de pintado.

func (*Scene) Push

func (s *Scene) Push(r geom.Rect)

Push restringe el recorte a la intersección con el actual y lo apila.

func (*Scene) Reset

func (s *Scene) Reset(bounds geom.Rect)

Reset deja la escena lista para un fotograma nuevo, conservando la memoria ya reservada. Se llama una vez por fotograma: la escena se reutiliza para que dibujar no genere basura y el recolector no interrumpa el scroll.

func (*Scene) Stroke

func (s *Scene) Stroke(r geom.Rect, c geom.Color, sides Sides, w float32)

Stroke emite un borde de un píxel lógico por los lados indicados.

El borde va por dentro del rectángulo. Es la convención que hace que dos paneles adyacentes con borde compartan la línea en lugar de dibujar dos, que a escalas fraccionarias se ve como un filo grueso e irregular.

func (*Scene) Text

func (s *Scene) Text(face text.FaceID, str string, at geom.Point, c geom.Color)

Text emite una cadena con su esquina superior izquierda en at.

Se toma la caja de línea y no la línea base a propósito: para disponer interfaz lo natural es "esta fila empieza aquí", y calcular la base a partir del ascenso es cosa del motor tipográfico.

type Scroll

type Scroll struct {
	Base

	Child Element
	Axis  Axis
	Theme *Theme

	// Step es cuánto avanza una muesca de la rueda, en píxeles.
	Step float32
	// contains filtered or unexported fields
}

Scroll desplaza a un hijo más grande que el hueco disponible.

El hijo se mide sin límite en el eje del desplazamiento y se sitúa desplazado hacia arriba —o hacia la izquierda—, recortado a la ventana visible. Es la solución correcta para contenido que se materializa entero; cuando el contenido son cien mil filas, lo que hace falta es virtualizar, y de eso se encarga List.

func NewScroll

func NewScroll(th *Theme, child Element) *Scroll

NewScroll envuelve a un hijo en un desplazamiento vertical.

func (*Scroll) Children

func (s *Scroll) Children() []Element

Children implementa Container.

func (*Scroll) Event

func (s *Scroll) Event(e Event) Handled

Event convierte la rueda en desplazamiento.

func (*Scroll) Layout

func (s *Scroll) Layout(c Constraints) geom.Size

Layout mide al hijo sin acotar el eje del desplazamiento y ocupa todo el hueco que le den.

func (*Scroll) Paint

func (s *Scroll) Paint(sc *Scene)

Paint recorta al hijo y añade la barra.

func (*Scroll) Place

func (s *Scroll) Place(r geom.Rect)

Place sitúa al hijo desplazado.

func (*Scroll) Scroller

func (s *Scroll) Scroller() *Scroller

Scroller da acceso a la mecánica, para desplazar desde fuera.

func (*Scroll) Tick

func (s *Scroll) Tick(dt time.Duration) bool

Tick implementa Animator: mientras haya inercia, la ventana se repinta.

type ScrollEvent

type ScrollEvent struct {
	Delta geom.Point
	Pos   geom.Point
	Mods  Mods
}

ScrollEvent es un desplazamiento de rueda o de trackpad sobre un punto. Delta se expresa en líneas, con el signo del contenido: positivo en Y avanza hacia el final.

type Scroller

type Scroller struct {
	// Unit es cuántos píxeles mide una unidad de desplazamiento; cero equivale
	// a uno, que es lo normal.
	//
	// Existe por el visor de código. Un archivo de treinta millones de líneas
	// mide setecientos millones de píxeles, y un float32 solo distingue
	// dieciséis millones de valores enteros: guardar ahí el desplazamiento
	// dejaría saltos de sesenta píxeles entre posiciones consecutivas. Con la
	// unidad puesta en la altura de línea, el desplazamiento se cuenta en
	// líneas y los umbrales de la inercia se reescalan solos.
	Unit float32
	// contains filtered or unexported fields
}

Scroller es la mecánica del desplazamiento con inercia, sin ser un elemento.

Está separada del widget porque hay dos clientes con necesidades distintas: Scroll, que desplaza a un hijo real, y List, que no tiene hijos porque virtualiza sus filas. Compartir la mecánica hace que ambos se sientan igual al usarlos, que es justo lo que se espera de un desplazamiento.

func (*Scroller) EnsureVisible

func (s *Scroller) EnsureVisible(from, to float32)

EnsureVisible desplaza lo mínimo para que un intervalo del contenido quede a la vista.

Se mueve lo justo y no se recentra a propósito: recentrar la vista en cada pulsación de flecha desorienta, porque el punto de referencia que el ojo estaba siguiendo cambia de sitio sin motivo.

func (*Scroller) Impulse

func (s *Scroller) Impulse(px float32)

Impulse aplica un empujón de la rueda, en píxeles.

El salto es inmediato y además deja velocidad: si el desplazamiento fuera solo la integral de la velocidad, la rueda respondería un fotograma tarde y se sentiría pastosa; si fuera solo el salto, no habría deslizamiento y cada muesca de la rueda se notaría como un escalón.

func (*Scroller) Max

func (s *Scroller) Max() float32

Max es el desplazamiento máximo posible.

func (*Scroller) Moving

func (s *Scroller) Moving() bool

Moving indica si hay inercia pendiente.

func (*Scroller) Offset

func (s *Scroller) Offset() float32

Offset es el desplazamiento actual, en píxeles lógicos.

func (*Scroller) SetExtents

func (s *Scroller) SetExtents(view, content float32)

SetExtents actualiza las medidas y reajusta el desplazamiento si el contenido ha encogido. Sin ese reajuste, cerrar una carpeta del árbol dejaría la vista mirando más allá del final de la lista.

func (*Scroller) SetOffset

func (s *Scroller) SetOffset(v float32)

SetOffset salta a una posición concreta y detiene la inercia: un salto explícito y un deslizamiento en curso son órdenes contradictorias.

func (*Scroller) Thumb

func (s *Scroller) Thumb(track geom.Rect, axis Axis) (geom.Rect, bool)

Thumb devuelve el rectángulo del pulgar dentro de una pista, y si hay que dibujarlo. No lo hay cuando todo el contenido cabe: una barra que ocupa toda la pista no informa de nada y ensucia.

func (*Scroller) Tick

func (s *Scroller) Tick(dt time.Duration) bool

Tick avanza la inercia y devuelve si queda movimiento.

type Segment

type Segment struct {
	Text string

	// Color a cero usa el atenuado del tema.
	Color geom.Color

	// OnClick lo convierte en pulsable. Sin él, el segmento es solo
	// información y no reacciona al puntero.
	OnClick func()
	// contains filtered or unexported fields
}

Segment es un trozo de la barra.

type Sides

type Sides uint8

Sides es un conjunto de lados, para los bordes.

const (
	Top Sides = 1 << iota
	Right
	Bottom
	Left

	// AllSides es el marco completo.
	AllSides = Top | Right | Bottom | Left
)

Lados combinables con OR.

func (Sides) Has

func (s Sides) Has(x Sides) bool

Has indica si el conjunto incluye un lado.

type Sized

type Sized struct {
	Base
	Child Element
	W, H  float32
	// contains filtered or unexported fields
}

Sized fija el tamaño de un hijo, o reserva un hueco si no lo hay.

Un cero en cualquiera de las dos dimensiones significa "la natural", de modo que Sized{W: 232} fija el ancho del panel lateral y deja que el alto lo decida quien reparte.

func Fixed

func Fixed(child Element, w, h float32) *Sized

Fixed envuelve a un hijo con un tamaño fijo.

func (*Sized) Children

func (s *Sized) Children() []Element

Children implementa Container.

func (*Sized) Layout

func (s *Sized) Layout(c Constraints) geom.Size

Layout impone el tamaño fijado.

func (*Sized) Paint

func (s *Sized) Paint(sc *Scene)

Paint pinta al hijo, si lo hay.

func (*Sized) Place

func (s *Sized) Place(r geom.Rect)

Place traslada el rectángulo al hijo.

type Spacer

type Spacer struct {
	Base

	// Min es el hueco mínimo, para separar sin depender del reparto.
	Min float32
}

Spacer es un hueco vacío. Añadido con flex empuja a sus vecinos hacia los extremos, que es como se alinea a la derecha una barra de estado.

func Flexible

func Flexible() *Spacer

Flexible crea un separador que se come el espacio sobrante.

func Gap

func Gap(size float32) *Spacer

Gap crea un separador rígido.

func (*Spacer) Layout

func (s *Spacer) Layout(c Constraints) geom.Size

Layout devuelve el mínimo pedido.

func (*Spacer) Paint

func (s *Spacer) Paint(*Scene)

Paint no pinta nada: un hueco es la ausencia de primitivas.

type Stack

type Stack struct {
	Base
	// contains filtered or unexported fields
}

Stack superpone a sus hijos en el mismo rectángulo, en orden de pintado.

Es lo que sostiene los elementos flotantes: el contenido va abajo y el panel emergente encima, compartiendo espacio en lugar de repartirlo.

func NewStack

func NewStack(children ...Element) *Stack

NewStack crea una pila con los hijos dados, del fondo a la superficie.

func (*Stack) Add

func (s *Stack) Add(e Element) *Stack

Add apila un hijo por encima de los anteriores.

func (*Stack) Children

func (s *Stack) Children() []Element

Children implementa Container.

func (*Stack) Layout

func (s *Stack) Layout(c Constraints) geom.Size

Layout mide a todos con las mismas restricciones y devuelve el mayor.

func (*Stack) Paint

func (s *Stack) Paint(sc *Scene)

Paint pinta del fondo a la superficie.

func (*Stack) Place

func (s *Stack) Place(r geom.Rect)

Place da a todos el rectángulo completo.

type StatusBar

type StatusBar struct {
	Base

	Theme *Theme

	Left  []Segment
	Right []Segment
	// contains filtered or unexported fields
}

StatusBar es la barra inferior: segmentos a la izquierda y a la derecha, sin nada en el medio.

El reparto no es un capricho de estilo. La izquierda es contexto —qué archivo, qué rama— y crece hacia la derecha; la derecha son estados cortos y fijos —línea y columna, codificación— y crece hacia la izquierda. Poniendo cada cosa en su extremo, ninguna se mueve cuando la otra cambia de longitud, que es lo que hace que un dato de la barra de estado se pueda leer de reojo.

func NewStatusBar

func NewStatusBar(th *Theme) *StatusBar

NewStatusBar crea la barra inferior.

func (*StatusBar) Event

func (b *StatusBar) Event(e Event) Handled

Event dispara los segmentos pulsables.

func (*StatusBar) Layout

func (b *StatusBar) Layout(c Constraints) geom.Size

Layout ocupa el ancho disponible y el alto del tema.

func (*StatusBar) Paint

func (b *StatusBar) Paint(s *Scene)

Paint dibuja el fondo, el borde superior y los segmentos.

func (*StatusBar) SetHover

func (b *StatusBar) SetHover(h bool)

SetHover implementa Hoverable.

type Strings

type Strings []string

Strings es el modelo más simple posible, para menús y listas fijas.

func (Strings) Count

func (s Strings) Count() int

Count implementa ListModel.

func (Strings) Row

func (s Strings) Row(i int) ListRow

Row implementa ListModel.

type Tab

type Tab struct {
	Title string

	// Modified marca el contenido sin guardar. Se dibuja como un punto en el
	// sitio de la cruz, que es la convención de todos los editores y evita
	// cerrar sin querer algo que no se ha guardado.
	Modified bool

	// Data es lo que la aplicación asocie a la pestaña: un documento, una ruta.
	Data any
}

Tab es una pestaña.

type Tabs

type Tabs struct {
	Base

	Theme *Theme
	Items []Tab

	// Active es la pestaña visible, o -1 si no hay ninguna.
	Active int

	OnActivate func(i int)
	OnClose    func(i int)
	// contains filtered or unexported fields
}

Tabs es la barra de pestañas del editor.

func NewTabs

func NewTabs(th *Theme) *Tabs

NewTabs crea una barra de pestañas.

func (*Tabs) Event

func (t *Tabs) Event(e Event) Handled

Event activa y cierra pestañas.

func (*Tabs) Layout

func (t *Tabs) Layout(c Constraints) geom.Size

Layout mide cada pestaña por su título y ocupa el ancho disponible.

Las pestañas no se reparten el ancho a partes iguales: una pestaña estrecha con un nombre largo obliga a recortar el nombre, que es lo único que sirve para distinguirlas. Se les da lo que piden y, cuando no caben, las últimas quedan fuera de la vista.

func (*Tabs) Paint

func (t *Tabs) Paint(s *Scene)

Paint dibuja la barra y sus pestañas.

func (*Tabs) SetHover

func (t *Tabs) SetHover(h bool)

SetHover implementa Hoverable.

type Theme

type Theme struct {
	Name    string
	Color   Palette
	Metrics Metrics
	Fonts   Fonts
}

Theme reúne el color, las medidas y las fuentes de la interfaz.

Se pasa a los widgets por puntero y no se copia: cambiar de tema en caliente —lo que hará la configuración del Hito 5— debe verse en todo lo que ya está construido sin reconstruir el árbol.

func DarkTheme

func DarkTheme(f Fonts) *Theme

DarkTheme es el tema por omisión: oscuro, de contraste medio y sin adornos.

La elección de tonos no es arbitraria. El fondo es un azul muy desaturado en lugar de negro puro porque el negro absoluto contra texto claro produce halos en pantallas OLED y cansa antes; los grises tienen todos la misma dominante para que las superficies se distingan por luminosidad y no por matiz, que es lo que hace que una interfaz se lea a la primera; y hay un único color de énfasis, porque si todo destaca no destaca nada.

type Tree

type Tree struct {
	*List

	Roots []*TreeNode

	// OnOpen se llama al activar una hoja. Las ramas no lo disparan: activarlas
	// las despliega.
	OnOpen func(n *TreeNode)

	// OnExpand se llama la primera vez que se despliega una rama sin hijos
	// cargados. Es lo que permite leer una carpeta cuando se abre en lugar de
	// recorrer el proyecto entero al arrancar, que en un repositorio grande es
	// la diferencia entre abrir al instante y esperar.
	OnExpand func(n *TreeNode)
	// contains filtered or unexported fields
}

Tree es un árbol desplegable construido sobre List.

Reutiliza la lista en lugar de reimplementarla porque un árbol desplegado es exactamente una lista: la jerarquía solo influye en la sangría y en qué filas existen. Así el desplazamiento, la virtualización, la inercia y el teclado son los mismos, y se comportan igual, que es lo que el usuario espera de dos paneles del mismo programa.

func NewTree

func NewTree(th *Theme, roots []*TreeNode) *Tree

NewTree crea un árbol sobre unos nodos raíz.

func (*Tree) Count

func (t *Tree) Count() int

Count implementa ListModel.

func (*Tree) Event

func (t *Tree) Event(e Event) Handled

Event añade el despliegue con las flechas laterales y delega el resto en la lista.

Izquierda y derecha se comportan como en cualquier explorador de archivos: derecha despliega y, si ya estaba desplegada, entra en el primer hijo; izquierda pliega y, si ya estaba plegada, sube al padre.

func (*Tree) Node

func (t *Tree) Node(i int) *TreeNode

Node devuelve el nodo de una fila, o nil si el índice no existe.

func (*Tree) Rebuild

func (t *Tree) Rebuild()

Rebuild recalcula las filas visibles. Hay que llamarlo tras desplegar, plegar o cambiar los nodos.

func (*Tree) Row

func (t *Tree) Row(i int) ListRow

Row implementa ListModel.

func (*Tree) Selection

func (t *Tree) Selection() *TreeNode

Selection devuelve el nodo seleccionado, o nil.

func (*Tree) SetRoots

func (t *Tree) SetRoots(roots []*TreeNode)

SetRoots sustituye el contenido del árbol.

type TreeNode

type TreeNode struct {
	Name   string
	Detail string

	// Branch marca los nodos que se pueden desplegar aunque todavía no tengan
	// hijos cargados. Sin esta distinción, una carpeta vacía y un archivo serían
	// indistinguibles.
	Branch   bool
	Children []*TreeNode
	Expanded bool

	Dim bool

	// Data es lo que la aplicación quiera colgar del nodo: una ruta, un
	// identificador. El árbol no lo mira.
	Data any
}

TreeNode es un nodo del árbol.

Los hijos se guardan tal cual y no se aplanan al construirlo: quien alimenta el árbol es un directorio del disco, que se lee por niveles y a demanda, y mantener la forma jerárquica es lo que permitirá cargar una carpeta la primera vez que se abra en lugar de recorrer el proyecto entero al arrancar.

Jump to

Keyboard shortcuts

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