Documentation and some other stuff.

This commit is contained in:
2026-03-30 11:03:56 +02:00
parent 8349b65a83
commit 9dd8b895ae
13 changed files with 2619 additions and 1607 deletions

View File

@@ -1,256 +1,457 @@
#lang scribble/manual
@(require scribble/racket
scribble/example
@(require racket/base
racket/class
scribble/core
(for-label racket/base
racket/string
racket/class
racket/path
racket/file
net/url
"../private/wv-context.rkt"
"../private/wv-settings.rkt"))
"../private/wv-settings.rkt"
"../private/wv-element.rkt"
"../private/wv-input.rkt"
"../private/rgba.rkt"))
@title{wv-window}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
@defmodule[wv-window]
@title{Object-Oriented Interface: @racket[wv-window%]}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
Window abstraction built on top of @racketmodname[racket-webview].
This module exports the @racket[wv-window%] class and re-exports the APIs from
@racketmodname[wv-element], @racketmodname[wv-input], @racketmodname[rgba], and
@racketmodname[wv-settings].
@section{Overview}
A @racket[wv-window%] object represents one webview window.
A window belongs to a context, loads one HTML path within that context, manages
its own title and geometry, dispatches incoming events, and provides access to
element wrappers through @racket[element].
The class also provides synchronous wrappers around the asynchronous dialog
interface by using continuations internally.
@section{Class: wv-window%}
@defclass[wv-window% object% ()]{
An OO wrapper around a single webview window.
A @racket[wv-window%] object owns one low-level window created with
@racket[webview-create], keeps a per-window settings view, and offers methods for
window management, DOM element access, event binding, file dialogs, and message boxes.
Represents one window in a webview context.
The class is designed so that subclasses can override event methods such as
@racket[moved], @racket[resized], @racket[page-loaded], @racket[js-event],
@racket[navigation-request], @racket[can-close?], and @racket[closed].
The class wraps a lower-level window created through
@racketmodname[racket-webview] and connects it to the surrounding class-based
API.
@defconstructor[([parent (or/c #f (is-a?/c wv-window%)) #f]
[wv-context (is-a?/c wv-context%) (if (eq? parent #f)
(error "wv context is mandatory")
(get-field wv-context parent))]
[html-path path-string? (error "html path is mandatory")]
[settings (is-a?/c wv-settings%)
(send wv-context settings
[wv-context (if (eq? parent #f)
(error "wv context is mandatory")
(get-field wv-context parent))]
[html-path path-string?]
[settings (send wv-context settings
(string->symbol (format "~a" html-path)))]
[title string? "Racket Webview Window"]
[width (or/c #f exact-integer?) #f]
[height (or/c #f exact-integer?) #f]
[x (or/c #f exact-integer?) #f]
[y (or/c #f exact-integer?) #f]
[wv any/c #f])]{
Creates a new window.
[width (or/c exact-integer? #f) #f]
[height (or/c exact-integer? #f) #f]
[x (or/c exact-integer? #f) #f]
[y (or/c exact-integer? #f) #f])]{
The @racket[wv-context] argument supplies the underlying webview context.
If @racket[parent] is given, the default value of @racket[wv-context] is taken from it.
The @racket[html-path] argument identifies the HTML resource to open.
The default @racket[settings] object is derived from @racket[wv-context] using
@racket[html-path] as per-window settings key.
Creates a window.
The constructor creates the low-level window using @racket[webview-create] and then
calls @racket[init-size] to restore position and size from settings, falling back to
constructor arguments or internal defaults.
The constructor requires @racket[html-path]. If @racket[parent] is @racket[#f],
@racket[wv-context] is mandatory. If a parent window is supplied,
@racket[wv-context] defaults to the context of that parent.
The default value of @racket[settings] is obtained as:
@racketblock[
(send wv-context settings (string->symbol (format "~a" html-path)))
]
After construction, the class:
@itemlist[#:style 'compact
@item{creates the underlying webview window}
@item{installs the internal event handler}
@item{sets the title}
@item{initializes position and size from settings or defaults}]
If a parent is supplied, the underlying lower-level parent window is passed to
@racket[webview-create].
}
@defmethod*[([(context) (is-a?/c wv-context%)])]{
Returns the window context object associated with this window.
@defmethod[(context) any/c]{
Returns the window context object supplied at construction.
}
@defmethod*[([(win-context) symbol?])]{
Returns the symbolic context identifier derived from @racket[html-path].
This value is typically used as the settings section for the window.
@defmethod[(win-context) symbol?]{
Returns the settings context key for this window.
The value is computed as:
@racketblock[
(string->symbol (format "~a" html-path))
]
}
@defmethod*[([(info) hash?])]{
Returns a hash table with administrative information about the window.
The hash contains entries for at least @racket['wv-context], @racket['wv],
@defmethod[(info) hash?]{
Returns administrative information for the window as a hash table.
The returned hash contains the keys @racket['wv-context], @racket['wv],
@racket['html-path], and @racket['elements].
}
@defmethod*[([(element [id symbol?]) any/c]
[(element [id symbol?] [type symbol?]) any/c])]{
Returns the wrapper object for the DOM element with identifier @racket[id].
@defmethod[(element [id symbol?] [type symbol?] ...) any/c]{
If the element has not been wrapped before, a new wrapper object is created and cached.
When @racket[type] is omitted, the method attempts to determine the HTML input type via
JavaScript. Depending on the resolved type, it creates an instance of a more specific
wrapper class such as @racket[wv-input/text%], @racket[wv-input/date%],
@racket[wv-input/number%], or falls back to @racket[wv-element%].
Returns the wrapper object associated with the DOM element identified by
@racket[id].
Element wrappers are cached in an internal weak hash table. If no wrapper exists
yet, one is created and cached.
If no explicit @racket[type] is supplied, the method asks the browser for the
element's @tt{type} attribute. Depending on the resulting type, one of the
following classes is instantiated:
@itemlist[#:style 'compact
@item{@racket[wv-input/text%] for @racket['text]}
@item{@racket[wv-input/date%] for @racket['date]}
@item{@racket[wv-input/time%] for @racket['time]}
@item{@racket[wv-input/datetime%] for @racket['datetime-local]}
@item{@racket[wv-input/range%] for @racket['range]}
@item{@racket[wv-input/number%] for @racket['number]}
@item{@racket[wv-input/check%] for @racket['checkbox]}
@item{@racket[wv-input/radio%] for @racket['radio]}
@item{@racket[wv-input/color%] for @racket['color]}
@item{@racket[wv-element%] otherwise}]
Newly created wrappers receive this window through the @racket[window] init
argument and the element id through @racket[element-id].
}
@defmethod*[([(moved [x exact-integer?] [y exact-integer?]) void?])]{
Event hook called when the window has moved.
The default implementation stores the new position in the settings object.
Subclasses may override this method.
@defmethod[(moved [xx exact-integer?] [yy exact-integer?]) any/c]{
Updates the internal position and stores the new coordinates in
@racket[settings] under @racket['x] and @racket['y].
}
@defmethod*[([(resized [width exact-integer?] [height exact-integer?]) void?])]{
Event hook called when the window has been resized.
The default implementation stores the new size in the settings object.
Subclasses may override this method.
@defmethod[(resized [w exact-integer?] [h exact-integer?]) any/c]{
Updates the internal size and stores the new dimensions in
@racket[settings] under @racket['width] and @racket['height].
}
@defmethod*[([(window-state-changed [state any/c]) any/c])]{
Event hook called when the native window state changes, for example after show,
hide, or maximize notifications. The default implementation returns @racket[#t].
}
@defmethod[(window-state-changed [st symbol?]) any/c]{
@defmethod*[([(page-loaded [ok? any/c]) any/c])]{
Event hook called after the page load completes. The argument indicates whether the
load succeeded. The default implementation returns @racket[#t].
}
Called when the underlying window state changes.
@defmethod*[([(can-close?) any/c])]{
Event hook queried before closing the window.
If this method returns a true value, the default event handler closes the window.
The default implementation returns @racket[#t].
}
@defmethod*[([(closed) any/c])]{
Event hook called after the window has been closed.
@defmethod[(page-loaded [oke any/c]) any/c]{
Called when a @racket['page-loaded] event is received.
The default implementation returns @racket[#t].
}
@defmethod*[([(js-event [js-event hash?]) any/c])]{
Handles a JavaScript-originated event.
@defmethod[(can-close?) any/c]{
The default implementation looks up the wrapped element referenced by the event and
forwards the event to that element's dispatcher. If no matching element is known,
it returns @racket['wv-unhandled-js-event]. Subclasses may override this for
application-specific dispatch.
Called when a @racket['can-close?] event is received.
If this method returns a true value, the window closes itself by calling
@racket[close].
The default implementation returns @racket[#t].
}
@defmethod*[([(navigation-request [type symbol?] [url url?]) (or/c 'internal 'external)])]{
Handles a navigation request.
@defmethod[(closed) any/c]{
If the requested URL starts with the context base URL, the window navigates internally
using @racket[webview-set-url!] and returns @racket['internal]. Otherwise the URL is
opened externally using @racket[send-url] and the method returns @racket['external].
Called when a @racket['closed] event is received.
The default implementation returns @racket[#t].
}
@defmethod*[([(add-class! [selector-or-id string?] [class string?]) this])]{
Adds the CSS class @racket[class] to the elements selected by @racket[selector-or-id].
Delegates to @racket[webview-add-class!].
@defmethod[(js-event [js-event hash?]) any/c]{
Dispatches a JavaScript event received from the browser.
The incoming hash is expected to contain at least the keys @racket['evt],
@racket['id], and optionally @racket['data]. If the referenced element exists in
the internal element cache, the event is dispatched to that element by calling
its @racket[dispatch-event] method.
If no matching element wrapper exists, the method returns
@racket['wv-unhandled-js-event].
}
@defmethod*[([(remove-class! [selector-or-id string?] [class string?]) this])]{
Removes the CSS class @racket[class] from the elements selected by @racket[selector-or-id].
Delegates to @racket[webview-remove-class!].
@defmethod[(navigation-request [type symbol?] [url url?]) (or/c 'internal 'external)]{
Handles a navigation request event.
If the requested URL starts with the context base URL, the request is treated as
internal, loaded into the current window, and @racket['internal] is returned.
Otherwise the URL is opened externally using @racket[send-url], and
@racket['external] is returned.
}
@defmethod*[([(devtools) this])]{
Opens or activates the developer tools for the underlying webview.
@defmethod[(add-class! [selector-or-id (or/c symbol? string?)]
[cl (or/c symbol? string? list?)])
(is-a?/c wv-window%)]{
Adds one or more CSS classes to the selected elements and returns this window.
}
@defmethod*[([(move [x exact-integer?] [y exact-integer?]) this])]{
Moves the native window to the given screen coordinates.
@defmethod[(remove-class! [selector-or-id (or/c symbol? string?)]
[cl (or/c symbol? string? list?)])
(is-a?/c wv-window%)]{
Removes one or more CSS classes from the selected elements and returns this
window.
}
@defmethod*[([(resize [width exact-integer?] [height exact-integer?]) this])]{
Resizes the native window to the given dimensions.
@defmethod[(devtools) (is-a?/c wv-window%)]{
Opens the developer tools and returns this window.
}
@defmethod*[([(close) this])]{
Closes the native window.
@defmethod[(move [x exact-integer?] [y exact-integer?]) (is-a?/c wv-window%)]{
Moves the window and returns this window.
}
@defmethod*[([(bind! [selector string?]
[events (or/c symbol? (listof symbol?))]
[callback procedure?]) (listof any/c)])]{
Binds one or more DOM events to all elements matched by @racket[selector].
@defmethod[(resize [w exact-integer?] [h exact-integer?]) (is-a?/c wv-window%)]{
This method delegates the JavaScript side of the binding to @racket[webview-bind!],
creates or reuses wrapper objects for the matched elements, and installs
@racket[callback] as event callback on each wrapper. The return value is the list of
matched element wrapper objects.
Resizes the window and returns this window.
}
@defmethod*[([(unbind! [selector string?]
[events (or/c symbol? (listof symbol?))]) any/c])]{
Removes previously installed bindings for the selected elements and events.
This method delegates to @racket[webview-unbind!] and removes callbacks from the
corresponding cached wrapper objects.
@defmethod[(close) (is-a?/c wv-window%)]{
Closes the window and returns this window.
}
@defmethod*[([(file-dialog-done [flag symbol?]
[file any/c]
[dir any/c]
[filter any/c]) void?])]{
Continuation hook used internally to resume a pending file or directory dialog.
Applications normally do not call this method directly.
@defmethod[(bind! [selector (or/c symbol? string?)]
[events (or/c symbol? list?)]
[callback procedure?])
list?]{
Binds one or more DOM events.
The method first delegates to @racket[webview-bind!]. For each returned binding,
it connects the corresponding element wrapper to @racket[callback].
If @racket[events] is a symbol, it is treated as a single-element list.
The result is the list produced by mapping over the binding items. In practice,
this yields the corresponding cached element wrapper objects.
}
@defmethod*[([(choose-dir [title string?] [base-dir (or/c #f path-string?)])
(or/c 'showing path-string? #f)])]{
Shows a directory chooser.
@defmethod[(unbind! [selector (or/c symbol? string?)]
[events (or/c symbol? list?)])
list?]{
If the chooser is successfully shown, the method initially returns @racket['showing]
and later resumes with the selected directory path or @racket[#f] when the dialog is
canceled. If the dialog cannot be shown, it returns @racket[#f].
Only one file or directory dialog can be active at a time.
Removes one or more DOM event bindings.
The method first delegates to @racket[webview-unbind!], then disconnects the
corresponding callbacks from cached element wrappers.
If an element wrapper no longer has any event callbacks, it is removed from the
internal cache.
The returned value is the list produced by @racket[webview-unbind!].
}
@defmethod*[([(file-open [title string?]
[base-dir (or/c #f path-string?)]
[filters any/c])
(or/c 'showing list? #f)])]{
Shows a file-open dialog.
@defmethod[(set-title! [title string?]) any/c]{
If the dialog is accepted, the resumed result is a list whose contents are derived from
the low-level file dialog result. If the dialog is canceled or cannot be shown, the
result is @racket[#f]. Only one file or directory dialog can be active at a time.
Sets the window title.
}
@defmethod*[([(file-save [title string?]
[base-dir (or/c #f path-string?)]
[filters any/c])
(or/c 'showing list? #f)])]{
Shows a file-save dialog.
@defmethod[(file-dialog-done [flag symbol?]
[file any/c]
[dir any/c]
[filter any/c])
any/c]{
If the dialog is accepted, the resumed result is a list whose contents are derived from
the low-level file dialog result. If the dialog is canceled or cannot be shown, the
result is @racket[#f]. Only one file or directory dialog can be active at a time.
Internal continuation callback used by file and directory dialogs.
The default implementation resumes the pending continuation stored by the window.
}
@defmethod*[([(message-done [event symbol?]) void?])]{
Continuation hook used internally to resume a pending message box.
Applications normally do not call this method directly.
@defmethod[(choose-dir [title string?] [base-dir (or/c path-string? #f)])
(or/c 'showing path-string? #f)]{
Shows a directory chooser dialog.
At most one file or directory dialog can be active at a time. Attempting to open
another one raises an exception.
If @racket[base-dir] is @racket[#f], the user's home directory is used.
If the underlying dialog could not be started, the method returns @racket[#f].
If the dialog is shown successfully, the method returns @racket['showing] first
and later resumes through the internal continuation mechanism.
When resumed after completion:
@itemlist[#:style 'compact
@item{the selected directory is returned on success}
@item{@racket[#f] is returned if the dialog was canceled}]
}
@defmethod*[([(message [type symbol?]
[title string?]
[message string?]
[#:sub submessage string? ""])
(or/c 'showing symbol? #f)])]{
Shows a native message box.
@defmethod[(file-open [title string?]
[base-dir (or/c path-string? #f)]
[filters any/c])
(or/c 'showing list? #f)]{
If the message box is shown successfully, the method initially returns @racket['showing]
and later resumes with the button result, such as @racket['msgbox-ok],
@racket['msgbox-cancel], @racket['msgbox-yes], or @racket['msgbox-no].
If the message box cannot be shown, the method returns @racket[#f].
Only one message box can be active at a time.
Shows an open-file dialog.
At most one file or directory dialog can be active at a time. Attempting to open
another one raises an exception.
If @racket[base-dir] is @racket[#f], the user's home directory is used.
If the underlying dialog could not be started, the method returns @racket[#f].
If the dialog is shown successfully, the method returns @racket['showing] first
and later resumes through the internal continuation mechanism.
When resumed after completion:
@itemlist[#:style 'compact
@item{on success, the method returns @racket[(cdr r)] from the internal dialog result}
@item{on cancellation, the method returns @racket[#f]}]
This matches the current source code exactly.
}
@defmethod*[([(init-size) void?])]{
Initializes the window position and size from the settings object.
If no persisted values are available, constructor arguments or module defaults are used.
@defmethod[(file-save [title string?]
[base-dir (or/c path-string? #f)]
[filters any/c])
(or/c 'showing list? #f)]{
Shows a save-file dialog.
At most one file or directory dialog can be active at a time. Attempting to open
another one raises an exception.
If @racket[base-dir] is @racket[#f], the user's home directory is used.
If the underlying dialog could not be started, the method returns @racket[#f].
If the dialog is shown successfully, the method returns @racket['showing] first
and later resumes through the internal continuation mechanism.
When resumed after completion:
@itemlist[#:style 'compact
@item{on success, the method returns @racket[(cdr r)] from the internal dialog result}
@item{on cancellation, the method returns @racket[#f]}]
This matches the current source code exactly.
}
@defmethod[(message-done [evt symbol?]) any/c]{
Internal continuation callback used by message boxes.
The default implementation resumes the pending message-box continuation stored by
the window.
}
@defproc[(root-file-not-found-handler [standard-file path-string?]
[not-found-handler procedure?])
@defmethod[(message [type symbol?]
[title string?]
[message string?]
[#:sub submessage string? ""])
(or/c 'showing symbol? #f)]{
Shows a message box.
At most one message box can be active at a time. Attempting to show another one
raises an exception.
If the underlying dialog could not be started, the method returns @racket[#f].
If the dialog is shown successfully, the method returns @racket['showing] first
and later resumes through the internal continuation mechanism.
When resumed after completion, the result is the event symbol returned by the
lower layer, such as the symbols corresponding to @tt{ok}, @tt{cancel},
@tt{yes}, or @tt{no}.
}
@defmethod[(init-size) any/c]{
Initializes the window position and size.
The method reads @racket['x], @racket['y], @racket['width], and
@racket['height] from @racket[settings]. If a value is missing, the constructor
argument is used if present; otherwise a default is chosen.
The resulting geometry is then applied through @racket[move] and @racket[resize].
}
}
@section{Events}
The window installs an internal event handler when it is created.
This handler reacts to the following event kinds produced by the lower layer:
@itemlist[#:style 'compact
@item{@racket['resize], forwarded to @racket[resized]}
@item{@racket['move], forwarded to @racket[moved]}
@item{@racket['page-loaded], forwarded to @racket[page-loaded]}
@item{@racket['show], @racket['hide], and @racket['maximize], forwarded to
@racket[window-state-changed]}
@item{@racket['can-close?], causing @racket[can-close?] to be queried}
@item{@racket['closed], forwarded to @racket[closed]}
@item{@racket['js-evt], decoded and forwarded to @racket[js-event]}
@item{@racket['navigation-request], forwarded to @racket[navigation-request]}
@item{file-dialog completion events, forwarded to @racket[file-dialog-done]}
@item{message-box completion events, forwarded to @racket[message-done]}]
Unhandled events are printed to the console by the current implementation.
@section{Function: root-file-not-found-handler}
@defproc[(root-file-not-found-handler [standard-file string?]
[not-found-handler procedure?] ...)
procedure?]{
Creates a file-resolution procedure that can be used as a fallback handler for a webview
context.
The returned procedure redirects requests for @tt{"/"} and, when needed,
@tt{"/index.html"} to @racket[standard-file]. For other missing files it either returns
the unresolved path unchanged or delegates to @racket[not-found-handler] when one is
supplied.
Creates a file-not-found handler for use with file serving.
The returned procedure has the shape:
@racketblock[
(lambda (file base-path path) ...)
]
Its behavior is as follows:
@itemlist[#:style 'compact
@item{if @racket[file] is @tt{"/"}, it retries using @racket[standard-file]}
@item{if @racket[file] is @tt{"/index.html"}, it falls back to
@racket[standard-file] when the target does not exist}
@item{if the requested path exists, it is returned}
@item{otherwise, if an extra @racket[not-found-handler] was supplied, that
handler is called}
@item{if no extra handler was supplied, the original path is returned}]
Only the first optional @racket[not-found-handler] is used.
}
@defthing[webview-version any/c]{
The version identifier exported by the underlying webview binding.
}
@section{Re-exports}
This module also re-exports the public APIs from:
@itemlist[#:style 'compact
@item{@racketmodname[wv-element]}
@item{@racketmodname[wv-input]}
@item{@racketmodname[rgba]}
@item{@racketmodname[wv-settings]}]
It also re-exports @racket[webview-version].