diff --git a/Readme.org b/Readme.org index 7497813..9653f26 100644 --- a/Readme.org +++ b/Readme.org @@ -241,314 +241,4 @@ value is. corner, its colours are strings, and its angles are radians, clockwise from the right, as ~sin~ and ~cos~ take them. -- Shapes: ~fill-rect~, ~stroke-rect~ (~#:radius~), ~fill-circle~, - ~stroke-circle~, ~fill-ellipse~, ~stroke-ellipse~, ~stroke-line~, - ~stroke-polyline~, ~fill-polygon~, ~stroke-arc~, and ~fill-path~ and - ~stroke-path~ with SVG path data. Lines take ~#:width~, ~#:cap~ and - ~#:dash~. -- Text and pictures: ~draw-text~ (~#:size~, ~#:bold~, ~#:font~, ~#:align~, - ~#:max-width~), ~text-size~ to lay text out, and ~draw-image~. -- ~translated~, ~scaled~, ~rotated~, ~clipped~ and ~faded~ change what a - function given to them draws. - -The drawing is vectors, so it is sharp at any scale. -[[file:examples/mixer.bjo][examples/mixer.bjo]] has knobs and a timeline with -a playhead, marks and a hover line. ~start-headless!~ sets Avalonia up with no -screen, so that a test can draw components and read their pixels; see -~tests/components.bjo~. - -* Controls bjogui does not wrap: (bjogui native) - -Any control of Avalonia's, or of another package's, can be put in a view. -The program makes the control and hooks its events itself, so this is the -one place where it names Avalonia's types. - -#+BEGIN_SRC scheme -(import (std interop)) -(import (bjogui native)) -(import/class (Control (: Avalonia.Controls.Control)) - (Calendar (: Avalonia.Controls.Calendar (-> Calendar))) - (DateTime (: System.DateTime)) - ((Nullable %a) (: System.Nullable))) -(import/extern - (selected-date (: Avalonia.Controls.Calendar.SelectedDate (-> Calendar (Nullable DateTime)) #:get)) - (selected-date! (: Avalonia.Controls.Calendar.SelectedDate (-> Calendar (Nullable DateTime) void) #:set)) - (parse-date (: System.DateTime.Parse (-> string DateTime)))) - -;; A calendar, for a date as "2026-10-25". -(: calendar (Native string string)) -(def calendar - (native (fun (send) ; makes one control - (def c (Calendar.)) - (ignore (add-event-handler! c "SelectedDatesChanged" - (: (fun (s e) - (def d (selected-date c)) - (when (.-HasValue d) (send (.ToString (.-Value d) "yyyy-MM-dd")))) - (-> Object Object void)))) - (cast Control c)) - (fun (control date) ; puts a value on it - (selected-date! (cast Calendar control) (parse-date date))))) - -;; In a view: -(hosted calendar (.date model) Picked) -#+END_SRC - -- ~(make send)~ makes the control once for its place in the view. ~send~ - gives an output to the app, as a message that ~hosted~ makes with - ~on-output~. It may be called from any thread. -- ~(put-value control value)~ is called after ~make~, and again each time the - view gives another value. What the control sends while it is given a value - is dropped, because it is not the user's. -- ~#:width~, ~#:height~ and ~#:margin~ are set on the control, as on any - view. A control measures itself, as Avalonia's do. -- Make a ~native~ once, at the top of a module. A view in the place of - another one is a new control. - -* Styles and themes - -[[file:docs/styles.png]] - -~run-app~'s ~#:theme~ gives ~"light"~, ~"dark"~, or anything else for the -system's, for a model, so a setting can switch it. ~#:styles~ is a list of -rules made by ~style~: a kind, by the name of its constructor, a class -given with ~classed~, and the look. - -#+BEGIN_SRC scheme -(run-app init gui-update view - #:theme (fun (m) (if (.dark m) "dark" "light")) - #:styles (list (style "text" #:class "title" #:font-size 22.0 #:colour "#f1c40f") - (style "button" #:class "primary" #:background "#27ae60" #:colour "white" - #:hover-background "#2ecc71" #:pressed-background "#1e8449"))) -#+END_SRC - -A rule takes ~#:colour~, ~#:background~, ~#:font-size~, ~#:padding~ and -~#:radius~. A button's rule also takes ~#:hover-colour~, ~#:hover-background~, -~#:pressed-colour~ and ~#:pressed-background~, which are set inside its -template, as the Fluent theme sets its own; no other kind has them. -[[file:examples/styles.bjo][examples/styles.bjo]] is the picture above. - -* Menus and keys - -#+BEGIN_SRC scheme -(menu-bar - (submenu "File" - (menu-item "New" New #:shortcut "Ctrl+N") - (menu-separator) - (menu-item "Quit" Quit #:shortcut "Ctrl+Q"))) -#+END_SRC - -A menu bar is a view, so it goes where the program puts it, usually at the -top of a ~dock~. A menu item's ~#:shortcut~ is shown beside it, and the keys -send its message while it is in the view and enabled. ~run-app~'s ~#:keys~ -gives more, for a model: ~(fun (m) (list (key "F5" Refresh)))~. Keys are -the window's after the control that has the keyboard had its chance, so a -text input keeps ~Ctrl+C~. - -* Work: commands and subscriptions - -~gui-update~ is a plain function, so it cannot wait, and the compiler says so -if it tries. Work is a value it returns, and the app runs it. - -A *command* is an ~(Event %m)~: an event whose answer is a message. ~(next m -command ...)~ is the next model and the commands to start; ~(next m)~ is the -model alone. The app syncs each command in a fiber of its own and gives its -message to ~gui-update~. Any event will do: a ~timeout~, a ~blocking~ call, a -channel, a ~choose~ of several. - -#+BEGIN_SRC scheme -(Load (next (record-set m (loading #t)) - (wrap (blocking (fun () (file-read-text path))) Loaded))) -#+END_SRC - -A *subscription* is a source of messages that runs while the model asks for -it. ~subscriptions~ gives the list for a model. After each update the app -starts those that are new and cancels those that are gone; the others keep -running. A subscription is known by its key. - -- ~(every duration msg)~ sends ~msg~ each time ~duration~ has passed. -- ~(subscription key next-event)~ is a source of your own: ~next-event~ gives - the event of the next message, and is called again after each one. - ~(subscription "jobs" (fun () (wrap (chan-recv jobs) Got)))~ sends what - comes on a channel. - -#+BEGIN_SRC scheme -(: subscriptions (-> Model (List (Sub Msg)))) -(defun (subscriptions m) - (if (.running m) (list (every (milliseconds 100) Tick)) '())) -#+END_SRC - -The fibers belong to the app. They post their messages to the UI thread, so -~gui-update~ and ~view~ always run there. When the window is closed they are -cancelled, and the program ends. - -** Commands that act on the controls and the clipboard - -Some commands do work of their own and send no message. Each finds its -control by the key of its view, given with ~keyed~, and runs once the -controls of the update are laid out, so a view the same update made is -there. - -| Command | Does | -|-------------------------------+---------------------------------------------------------| -| ~(focus key)~ | gives the keyboard to the control | -| ~(scroll-to-end key)~ | scrolls a ~scroll~ view to its end, as a log wants | -| ~(scroll-to-start key)~ | scrolls a ~scroll~ view to its start | -| ~(scroll-into-view key)~ | scrolls the views around the control until it is seen | -| ~(copy-text text)~ | puts ~text~ on the clipboard | -| ~(paste-text on-text)~ | sends ~(on-text (Some text))~, or ~(on-text None)~ | - -#+BEGIN_SRC scheme -(Add (next (record-set m (items (vec-add (.items m) (.draft m))) (draft "")) - (focus "draft") - (scroll-to-end "items"))) - -(keyed "draft" (text-input (.draft m) Draft)) -#+END_SRC - -* Windows and dialogs of your own - -[[file:docs/contacts.png]] [[file:docs/contacts-edit.png]] - -A program can have windows besides the main one. ~run-app~'s ~#:windows~ -gives the windows a model has, as ~view~ gives the main window's view, and -the app opens, changes and closes them to match. - -#+BEGIN_SRC scheme -(: windows (-> Model (List (WindowView Msg)))) -(defun (windows m) - (match (.draft m) - ((Some d) (list (window "edit" "Edit contact" Cancel (draft-view d) #:modal #t))) - (None '()))) - -(run-app init gui-update view #:windows windows) -#+END_SRC - -- ~(window key title on-close content #:width #:height #:modal)~ is a window, - known by its key. It is opened when it is first in the list, its view and - title are changed as the main window's view is while it stays, and it is - closed when it is gone. -- A click on its close button does not close it: it sends ~on-close~, and the - window closes when the next model has it no more. -- With no ~#:width~ or ~#:height~ it is as big as its content. -- A ~#:modal~ window is a dialog: the main window takes no input until it is - closed. So a dialog of one's own is a window whose state, such as a draft - being edited, is in the model, and whose view is a view like any other. -- ~window-map~ maps a window's messages, as ~view-map~ does a view's. -- The program ends when the main window is closed, and the others with it. - -[[file:examples/contacts.bjo][examples/contacts.bjo]] edits a contact in a -modal dialog, and shows the chosen one in a second window. - -* Dialogs: (bjogui dialogs) - -A dialog is a command, and its answer comes back as a message. - -| Command | Sends | -|-------------------------------------------+-----------------------------------------------| -| ~(alert text msg)~ | ~msg~ when it is closed | -| ~(confirm text on-answer)~ | ~(on-answer #t)~ for OK, ~#f~ otherwise | -| ~(prompt text on-answer #:value)~ | ~(on-answer (Some text))~, or ~None~ | -| ~(open-file on-answer #:filters)~ | ~(on-answer (Some path))~, or ~None~ | -| ~(open-files on-answer)~ | ~(on-answer paths)~ | -| ~(save-file on-answer #:name)~ | ~(on-answer (Some path))~, or ~None~ | -| ~(open-folder on-answer)~ | ~(on-answer (Some path))~, or ~None~ | -| ~(quit)~ | nothing: it closes the windows | - -#+BEGIN_SRC scheme -(Delete (next m (confirm "Delete it?" Deleted?))) -((Deleted? #t) (next (remove m))) -((Deleted? #f) (next m)) -#+END_SRC - -~#:filters~ are kinds of file, each a name and patterns: ~(list (Tuple "Text" -'("*.txt" "*.md")))~. ~run-app~'s ~#:on-close msg~ makes the close button -send ~msg~ instead of closing, and the program closes with ~(quit)~ when it -is ready, say after asking about unsaved work. - -A dialog belongs to the main window. With none, as under ~start-app~, each -answers as if it was cancelled. Avalonia's dialogs cannot be cancelled, so a -command that is cancelled stops waiting, and the dialog stays until it is -answered. - -* Running: (bjogui app) - -- ~(run-app init gui-update view #:subscriptions #:keys #:theme #:styles - #:windows #:on-close #:title #:width #:height)~ opens a window and runs - until it is closed. -- ~#:screenshot path~ makes ~run-app~ save a PNG of the window once it is - shown, at twice its size, and close it. Each other window that is open is - saved beside it, with its key after the name: ~shot-edit.png~. It is the - environment variable ~BJOGUI_SCREENSHOT~ unless the program says - otherwise, so any program can be pictured with no change: - ~BJOGUI_SCREENSHOT=docs/todo.png bjo run examples/todo.bjo~ made the - picture above. -- ~(start-app init gui-update view on-root #:subscriptions #:windows)~ makes - the controls with no window, and gives ~on-root~ the control to show. Its - fibers belong to the scope it is started in. - - ~app-dispatch!~ gives a message on the UI thread, and ~app-send!~ from any - thread. - - ~app-run-pending!~ runs what fibers have posted, where no loop of - Avalonia's does. - - ~app-stop!~ cancels the subscriptions and drops later messages. - - ~app-press!~ presses keys, such as ~"Ctrl+S"~, as the window does. - - ~app-input!~ gives an ~Input~ to the component at a path, as its control - does. - - ~app-model~ reads the model, and ~app-control-at~ finds a control by its - path of child indices. - - ~app-windows~ gives the keys of the other windows that are open, and - ~app-window-control~ finds a control in one of them. - - ~tests/app.bjo~ and ~tests/effects.bjo~ use these to drive an app through - its real controls. - -* Requirements - -- Avalonia 11.3, which ~bjo~ restores from NuGet. On Linux it needs X11 or - XWayland. -- A Bjolang with ~(std interop)~, which makes the delegates that Avalonia's - events take. - -* Tests - -#+BEGIN_SRC sh -bjo run tests/app.bjo # prints "bjogui ok" -bjo run tests/effects.bjo # prints "bjogui effects ok" -bjo run tests/controls.bjo # prints "bjogui controls ok" -bjo run tests/lists.bjo # prints "bjogui lists ok" -bjo run tests/inputs.bjo # prints "bjogui inputs ok" -bjo run tests/menus.bjo # prints "bjogui menus ok" -bjo run tests/dialogs.bjo # prints "bjogui dialogs ok" -bjo run tests/tree.bjo # prints "bjogui tree ok" -bjo run tests/canvas.bjo # prints "bjogui canvas ok" -bjo run tests/components.bjo # prints "bjogui components ok" -bjo run tests/native.bjo # prints "bjogui native ok" -bjo run tests/commands.bjo # prints "bjogui commands ok" -bjo run tests/windows.bjo # prints "bjogui windows ok" -bjo run examples/counter.bjo -bjo run examples/stopwatch.bjo -bjo run examples/todo.bjo -bjo run examples/styles.bjo -bjo run examples/sketch.bjo -bjo run examples/mixer.bjo -bjo run examples/contacts.bjo -#+END_SRC - -* Not yet - -- A data grid, and lists that make controls only for the items on screen. -- The macOS menu bar. -- Pointer-over and pressed colours for kinds other than buttons. -- A component asks for no size of its own: it is given one, or fills the - space it gets. Measuring itself, an accessibility tree, and text input - inside a component would need a subclass. -- A moved control is taken out of the panel and put back, so it may lose the - keyboard focus. -- A window's size and place are not kept from one run to the next. -- The keys of a menu item are looked for in the views of all the windows, - and not only in the window that has the keyboard. -- A component is drawn again whole when its value, its state or its size - changes. A few thousand shapes are fast; a very large drawing that changes - at 60 frames a second is not. -- Drag and drop, a tray icon, notifications and printing. -- The tests click a window's close button through the callback that the - platform calls, and not with a real pointer. +- Shapes: \ No newline at end of file