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 ¶
- type Align
- type Animator
- type Axis
- type Base
- type Box
- type CharEvent
- type Child
- type Constraints
- type Container
- type Dialog
- type Element
- type Event
- type Flow
- type FocusEvent
- type Focusable
- type Fonts
- type Handled
- type Hoverable
- type Input
- type Insets
- type Key
- type KeyEvent
- type Label
- type List
- func (l *List) AcceptsFocus() bool
- func (l *List) Event(e Event) Handled
- func (l *List) Focused() bool
- func (l *List) Layout(c Constraints) geom.Size
- func (l *List) Paint(s *Scene)
- func (l *List) Place(r geom.Rect)
- func (l *List) Reveal(i int)
- func (l *List) Scroller() *Scroller
- func (l *List) Select(i int)
- func (l *List) SetFocus(f bool)
- func (l *List) SetHover(h bool)
- func (l *List) Tick(dt time.Duration) bool
- type ListModel
- type ListRow
- type Metrics
- type Mods
- type MouseButton
- type MouseEvent
- type MouseKind
- type Overlay
- type Palette
- type Prim
- type PrimKind
- type Renderer
- type Root
- func (r *Root) Animate(now clock.Time) bool
- func (r *Root) Bounds() geom.Rect
- func (r *Root) Event(e Event) Handled
- func (r *Root) Focus(f Focusable)
- func (r *Root) FocusNext(back bool)
- func (r *Root) Focused() Focusable
- func (r *Root) Layout(size geom.Size)
- func (r *Root) Paint() *Scene
- func (r *Root) Scene() *Scene
- type Scene
- func (s *Scene) Clip() geom.Rect
- func (s *Scene) Dump() string
- func (s *Scene) Fill(r geom.Rect, c geom.Color)
- func (s *Scene) Number(n int) string
- func (s *Scene) Pop()
- func (s *Scene) Prims() []Prim
- func (s *Scene) Push(r geom.Rect)
- func (s *Scene) Reset(bounds geom.Rect)
- func (s *Scene) Stroke(r geom.Rect, c geom.Color, sides Sides, w float32)
- func (s *Scene) Text(face text.FaceID, str string, at geom.Point, c geom.Color)
- type Scroll
- type ScrollEvent
- type Scroller
- func (s *Scroller) EnsureVisible(from, to float32)
- func (s *Scroller) Impulse(px float32)
- func (s *Scroller) Max() float32
- func (s *Scroller) Moving() bool
- func (s *Scroller) Offset() float32
- func (s *Scroller) SetExtents(view, content float32)
- func (s *Scroller) SetOffset(v float32)
- func (s *Scroller) Thumb(track geom.Rect, axis Axis) (geom.Rect, bool)
- func (s *Scroller) Tick(dt time.Duration) bool
- type Segment
- type Sides
- type Sized
- type Spacer
- type Stack
- type StatusBar
- type Strings
- type Tab
- type Tabs
- type Theme
- type Tree
- type TreeNode
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
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.
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.
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 (*Box) Layout ¶
func (b *Box) Layout(c Constraints) geom.Size
Layout mide al hijo dentro de los márgenes.
func (*Box) Paint ¶
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) WithBackground ¶
WithBackground fija el color de fondo.
func (*Box) WithBorder ¶
WithBorder fija el color y los lados del borde.
func (*Box) WithPadding ¶
WithPadding fija los márgenes interiores.
type CharEvent ¶
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 ¶
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 ¶
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 ¶
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 ¶
AcceptsFocus implementa Focusable: el diálogo se queda el teclado mientras está abierto.
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.
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 (*Flow) Add ¶
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) 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 ¶
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.
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) IndexAt ¶
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 ¶
LineHeight es la separación entre líneas de una variante.
type Hoverable ¶
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 (*Input) AcceptsFocus ¶
AcceptsFocus implementa Focusable.
func (*Input) Cursor ¶
Cursor devuelve la posición del cursor en bytes, para las pruebas y para los widgets que se construyan encima.
func (*Input) Layout ¶
func (in *Input) Layout(c Constraints) geom.Size
Layout ocupa el ancho disponible y el alto de una fila cómoda.
type Insets ¶
type Insets struct {
Top, Right, Bottom, Left float32
}
Insets son márgenes por lado, en píxeles lógicos.
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.
type KeyEvent ¶
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 (*Label) Layout ¶
func (l *Label) Layout(c Constraints) geom.Size
Layout mide la cadena y reserva una línea de alto.
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 (*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) SetFocus ¶
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.
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.
Modificadores. Super es la tecla Windows o Command.
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.
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 ¶
NewOverlay crea un panel flotante, inicialmente oculto.
func (*Overlay) Children ¶
Children implementa Container. Con el panel oculto no se declaran hijos: así ni el hit-testing ni el foco pueden alcanzarlos.
func (*Overlay) Event ¶
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) Layout ¶
func (o *Overlay) Layout(c Constraints) geom.Size
Layout ocupa todo el hueco disponible: el velo cubre la ventana entera.
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.
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 ¶
NewRenderer crea un renderer sobre un atlas y su pintor.
type Root ¶
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 (*Root) Animate ¶
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) FocusNext ¶
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.
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) Dump ¶
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 ¶
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 ¶
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) Reset ¶
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 ¶
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.
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 (*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.
type ScrollEvent ¶
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 ¶
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 ¶
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) SetExtents ¶
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 ¶
SetOffset salta a una posición concreta y detiene la inercia: un salto explícito y un deslizamiento en curso son órdenes contradictorias.
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.
type Sized ¶
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.
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.
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.
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 (*StatusBar) Layout ¶
func (b *StatusBar) Layout(c Constraints) geom.Size
Layout ocupa el ancho disponible y el alto del tema.
type Strings ¶
type Strings []string
Strings es el modelo más simple posible, para menús y listas fijas.
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 (*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.
type Theme ¶
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 ¶
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 (*Tree) Event ¶
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) Rebuild ¶
func (t *Tree) Rebuild()
Rebuild recalcula las filas visibles. Hay que llamarlo tras desplegar, plegar o cambiar los nodos.
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.