Windows GUI Library
The klyn.gui.windows package provides Klyn's native widget layer for desktop
applications. It is built around typed widgets, layout managers, multicast events, and KSS
themes so application code stays explicit and statically typed.
Only klyn is imported by default. GUI applications must import the widget package
and, when needed, the layout, event, and binding packages explicitly.
import klyn.gui.windows
import klyn.gui.windows.layouts
import klyn.gui.events
import klyn.binding
The main classes are MainWindow, Container, Label,
TextBox, Button, ComboBox, and the layout managers in
klyn.gui.windows.layouts.
The frame property selects who paints the outer frame. The default is the native
operating-system frame. Frame mode can be changed while a window is visible; the backend keeps
the client content at the same screen position and size while decorations are reapplied.
| Mode | Behavior |
|---|---|
WindowFrame.NATIVE_FRAME | Operating-system title bar and borders. This is the default. |
WindowFrame.KLYN_FRAME | Undecorated native surface with a themed Klyn title bar and border. Drag the title bar to move the window, double-click it to maximize or restore, and use its minimize, maximize/restore and close buttons normally. |
WindowFrame.NO_FRAME | No native or Klyn frame; only the root content is rendered. |
win = MainWindow("Frame preview")
win.frame = WindowFrame.KLYN_FRAME
# The mode can also be changed after show().
win.frame = WindowFrame.NO_FRAME
win.frame = WindowFrame.NATIVE_FRAME
size always means graphical dimensions on every Widget, including
containers. It is a Tuple<Int, Int> containing width and height. Collection-like
counts use names that describe what is counted.
panel = Container()
panel.size = (640, 480)
panel.add(Label("First child"))
assert panel.childCount == 1uL
combo = ComboBox()
combo.addItem("Light")
combo.addItem("Dark")
assert combo.itemCount == 2uL
Container, TabWidget, and DockBar expose
childCount. ComboBox exposes itemCount. Do not use
size as an element count for GUI widgets.
A MainWindow owns a centralWidget. Add widgets to containers and let
a layout manager compute bounds. This keeps resize behavior in one place and avoids manual
coordinate code in ordinary forms.
import klyn.gui.windows
import klyn.gui.windows.layouts
win = MainWindow("Hello")
win.size = (420, 240)
root = win.centralWidget
root.layout = VBoxLayout(spacing=12)
root.padding = [24, 24, 24, 24]
title = Label("Welcome to Klyn")
root.add(title)
field = TextBox()
field.placeholder = "Your name"
root.add(field)
button = Button("Continue")
root.add(button)
win.centerIn(null)
win.show()
win.run()
Layouts currently include VBoxLayout, HBoxLayout,
GridLayout, FormLayout, FlowLayout, and
DockLayout. For form-oriented screens, FormLayout is usually a better
starting point than absolute positioning.
GUI events are multicast. Use += to subscribe and -= to unsubscribe.
A single event can have several handlers, so a widget can notify both application logic and
instrumentation code without replacing the previous callback. See
Signals vs Events for the precise split between
observable state and rich UI actions.
import klyn.gui.windows
import klyn.gui.events
class Controller:
public onSave(action as ActionEvent) as Void:
print("save requested")
controller = Controller()
button = Button("Save")
button.clicked += controller::onSave
button.clicked += lambda(e: ActionEvent): print(e.source)
Method references use object::method. Lambdas can capture values from the
surrounding scope and should keep an explicit event parameter type when the event payload
matters.
Core widgets expose the common GUI event set through klyn.gui.events:
clicked, pressed, released,
keyPressed, keyReleased, mousePressed,
mouseReleased, mouseMoved, resized,
closed, and paintRequested. Use these for user actions and lifecycle
notifications; keep ordinary mutable state as signal properties.
Windows-only event payloads are grouped under klyn.gui.windows.events. Import it
when handling TreeView and TableView specialized events.
import klyn.gui.windows
import klyn.gui.windows.events
tree.nodeActivated += lambda(e: TreeNodeEvent): print(e.node.text)
table.cellSelected += lambda(e: TableCellEvent): print(e.row)
Observable widget state is declared as a signal property. The notification object is
intentionally not exposed as a separate member; use Binding.watch,
Binding.oneWay, or Binding.twoWay with member references.
import klyn.binding
import klyn.gui.windows
slider = Slider(value=50, minimum=0, maximum=100)
spin = SpinBox(value=50, minimum=0, maximum=100)
Binding.twoWay(slider::value, spin::value)
Binding.watch(slider::value, lambda(value: Object): print(value))
Use event clicked as ActionEvent for rich UI actions. Use
signal property value as Int for state that should participate in data binding.
The dedicated Signals vs Events page explains when to
choose each tool.
Use Timer for periodic GUI work such as blinking indicators, clocks, previews, or
lightweight polling. Timer callbacks are dispatched by the native event loop, not by an
arbitrary worker thread.
import klyn.gui.windows
import klyn.gui.events
canvas = MyCanvas()
timer = Timer(1000)
timer.timeout += lambda(e: ActionEvent): canvas.repaint()
timer.start()
Stop timers when they are no longer needed. samples/gui/ClockSample.kn uses this
pattern to repaint an analog clock once per second.
Text fields expose editing state through TextBox. Password fields use
echoMode, and buttons can be marked as the default action for Enter-driven forms.
The native backend also supports Tab and Shift+Tab focus traversal.
Text can be selected with the mouse or with Shift and the navigation keys. The standard
Ctrl+A, Ctrl+C, Ctrl+X, and Ctrl+V
shortcuts work consistently on Windows and X11; read-only fields remain selectable and copyable.
Use selectedText when application code needs the active selection. Clipboard export is
deliberately disabled for password fields.
address = TextBox("https://klyn.deepcodia.fr/")
address.select(0, 5)
assert address.selectedText == "https"
import klyn.gui.windows
import klyn.gui.windows.layouts
import klyn.gui.events
public class LoginWindow extends MainWindow:
private login as TextBox
private password as TextBox
private status as Label
public LoginWindow():
super("Login")
this.size = (420, 260)
root = this.centralWidget
root.layout = FormLayout(rowSpacing=12, columnSpacing=16)
root.padding = [24, 24, 24, 24]
form = root.layout as FormLayout
this.login = TextBox()
this.login.placeholder = "login"
form.addRow("&Login:", this.login)
this.password = TextBox()
this.password.echoMode = TextBox.ECHO_PASSWORD
this.password.placeholder = "password"
form.addRow("&Password:", this.password)
submit = Button("Connect")
submit.isDefaultButton = true
submit.clicked += this::connect
form.addRow(submit)
this.status = Label("")
form.addRow(this.status)
private connect(action as ActionEvent) as Void:
if this.login.text.trim() == "":
this.status.text = "Login is required"
return
this.status.text = "Connected"
win = LoginWindow()
win.centerIn(null)
win.show()
win.run()
The klyn.gui.images package provides an abstract Image API and a
factory that selects the concrete decoder from the extension. Current formats are SVG, BMP,
GIF, JPEG (.jpg or .jpeg), and TIFF
(.tif or .tiff).
import klyn.gui.images
icon = Image.getImage("assets/project.svg")
icon.resize(32, 32)
photo = Image.getImage("assets/photo.jpg")
photo.crop(10, 10, 320, 200)
assert photo.width == 320
assert photo.height == 200
Raster formats share their decoded-image behavior through RasterImage. SVG keeps a
vector representation: resize() changes the logical view-box dimensions and
crop() changes its origin and size without rasterizing the source.
The default widget appearance is defined with KSS, a small CSS-like styling layer. Built-in
themes live under KLYN_HOME/lib/klyn/gui/windows/themes as
light.kss and dark.kss. The default mode is system, so
Klyn chooses the light or dark theme from the operating-system appearance when it can.
import klyn.gui.windows
import klyn.gui.windows.themes
ThemeManager.setAppearanceMode(ThemeManager.MODE_DARK)
win = MainWindow("Themed")
win.appearanceMode = ThemeManager.MODE_SYSTEM
KSS currently covers the common surface properties used by the widget set: foreground color,
background color, margins, padding, font family, font size, borders, corner radius, placeholder
color, selection color, caret color, and slider track fill color. Slider thumbs use the
resolved accent-color, while the active track segment can be overridden with
track-fill-color. Prefer the built-in theme files for default widget styling;
application-specific colors should live in application code or a dedicated app theme. See GUI Themes and KSS for mode control, custom stylesheets, and the supported KSS subset.
Every Windows GUI application gets a global runtime content zoom. Hold Ctrl and use the mouse wheel to increase or decrease the complete window content in real time: fonts, KSS metrics, layout spacing, controls and modal lightboxes all follow the same factor. Hold Ctrl and double-click the middle mouse button to reset the zoom and resize the window back to its original client size.
The zoom is applied above KSS declarations and above the automatic screen-density scale. The native backend keeps the original window client size captured before the first zoom gesture, so reset is deterministic. Application code usually does not need to handle these gestures manually. Zoom uses a dedicated fast path: it queues a relayout/repaint without reapplying the complete KSS tree for every wheel tick.
A maximized or fullscreen native window keeps its operating-system bounds while its content scales. For a normal window, zoom adjusts the client size without moving its top-left screen position. Reset restores the original client size.
import klyn.gui.windows
import klyn.gui.windows.themes
win = MainWindow("Readable UI")
# Optional programmatic access for menus or settings dialogs.
win.adjustContentZoom(1)
print(ThemeManager.contentZoom)
win.resetContentZoom()
ThemeManager.adjustContentZoom() updates the global zoom factor. The older
adjustFontZoom() and fontZoom names remain as compatibility aliases, but
new code should use the content-oriented names.
Window.capture() renders the window client area to a BMP file through Klyn's own
painter pipeline. It is useful for documentation, visual regression checks, and tuning layouts
without relying on an external screenshot tool.
win = MainWindow("Preview")
win.size = (640, 420)
win.capture("preview.bmp")
Captures do not include operating-system window decorations. They show the same widget tree that Klyn paints inside the client area.
Open samples/gui/Calculator.kn for a larger GUI program using layouts, buttons,
focus feedback, and event handlers. Continue with Signals vs Events for the event/binding model, GUI Themes and KSS for appearance control, FormLayout for label/field forms, Slider Widget for scale input, DatePicker for calendar input, DockWorkspace for IDE-like workbenches, Terminal Widget for embedded shells, then Canvas and
Custom Painting when you need a widget that draws its own pixels.