documentation

This commit is contained in:
2026-04-01 16:23:56 +02:00
parent 5ee62d0064
commit ab666368b1
27 changed files with 1080 additions and 164 deletions

View File

@@ -23,36 +23,34 @@
(define-runtime-path dialog-html "example-1-dialog.html") (define-runtime-path dialog-html "example-1-dialog.html")
(define-runtime-path cur-dir ".") (define-runtime-path cur-dir ".")
#| (define test-menu (wv-menu 'main-menu
(define test-menu (menu 'main-menu (wv-menu-item 'm-file "File"
(menu-item 'm-file "File" #:submenu
#:submenu (wv-menu (wv-menu-item 'm-open "Open File")
(menu (menu-item 'm-open "Open File") (wv-menu-item 'm-close "Close File")
(menu-item 'm-close "Close File") (wv-menu-item 'm-select-dir "Select Folder" #:separator #t)
(menu-item 'm-select-dir "Select Folder" #:separator #t) (wv-menu-item 'm-quit "Quit" #:separator #t)))
(menu-item 'm-quit "Quit" #:separator #t))) (wv-menu-item 'm-edit "Edit"
(menu-item 'm-edit "Edit" #:submenu
#:submenu (wv-menu (wv-menu-item 'm-copy "Copy")
(menu (menu-item 'm-copy "Copy") (wv-menu-item 'm-cut "Cut")
(menu-item 'm-cut "Cut") (wv-menu-item 'm-paste "Paste")
(menu-item 'm-paste "Paste") (wv-menu-item 'm-prefs "Preferences" #:separator #t)
(menu-item 'm-prefs "Preferences" #:separator #t) ))
)) (wv-menu-item 'm-auto "Processes"
(menu-item 'm-auto "Processes" #:submenu
#:submenu (wv-menu (wv-menu-item 'm-start "Start counter")
(menu (menu-item 'm-start "Start counter") (wv-menu-item 'm-sub "Submenu"
(menu-item 'm-sub "Submenu" #:submenu
#:submenu (wv-menu (wv-menu-item 'm-sub1 "Submenu 1")
(menu (menu-item 'm-sub1 "Submenu 1") (wv-menu-item 'm-sub2 "Submenu 2")
(menu-item 'm-sub2 "Submenu 2") (wv-menu-item 'm-sub3 "Submenu 3")
(menu-item 'm-sub3 "Submenu 3") )
) )
) (wv-menu-item 'm-stop "Stop counter")
(menu-item 'm-stop "Stop counter") )
) )
) ))
))
|#
(define example-1-dialog% (define example-1-dialog%
(class wv-dialog% (class wv-dialog%
@@ -222,7 +220,7 @@
) )
(define/override (page-loaded oke) (define/override (page-loaded oke)
(ww-debug (format "HTML LOADED ~a" oke)) (ww-debug (format "HTML LOADED ~a ~a" oke (current-milliseconds)))
(set! has-page oke) (set! has-page oke)
(super page-loaded oke) (super page-loaded oke)
(displayln "super called") (displayln "super called")
@@ -259,11 +257,13 @@
(λ (el evt data) (λ (el evt data)
(send this choose-dir*))) (send this choose-dir*)))
) )
(displayln "page-loaded done")
)
;(ww-debug "SETTING MENU") (displayln (format "setting menu ~a" (current-milliseconds)))
#|(let* ((div-open (send this element 'div-open)) (send this set-menu! test-menu)
(send this connect-menu! 'm-quit (λ ()
(send this reset-counter)
(send this close)))
(let* ((div-open (send this element 'div-open))
(c-open 0) (c-open 0)
(div-close (send this element 'div-close)) (div-close (send this element 'div-close))
(c-close 0) (c-close 0)
@@ -273,39 +273,29 @@
(c-cut 0) (c-cut 0)
(div-paste (send this element 'div-paste)) (div-paste (send this element 'div-paste))
(c-paste 0) (c-paste 0)
(mk (λ (item el str count)
(send this connect-menu! item
(λ ()
(send el set-innerHTML! (format "~a ~a" str (count))))
)
)
)
) )
(mk 'm-open div-open "Open file" (inc c-open))
(mk 'm-close div-close "Close file" (inc c-close))
(mk 'm-copy div-copy "Edit Copy" (inc c-copy))
(mk 'm-cut div-cut "Edit Cut" (inc c-cut))
(mk 'm-paste div-paste "Edit Paste" (inc c-paste))
)
(send this set-menu! test-menu) (send this connect-menu! 'm-start (λ () (send this start-counter)))
(send this connect-menu! 'm-quit (send this connect-menu! 'm-stop (λ () (send this reset-counter)))
(λ () (send this connect-menu! 'm-prefs (λ () (send this prefs)))
(send this reset-counter) (send this connect-menu! 'm-select-dir (λ () (send this choose-dir*)))
(send this close))
)
(let ((make-menu-executor (λ (item elem string count)
(send this connect-menu! item
(λ ()
(send elem set-inner-html! (format "~a ~a" string (count)))))))
)
(make-menu-executor 'm-open div-open "Open file" (inc c-open))
(make-menu-executor 'm-close div-close "Close file" (inc c-close))
(make-menu-executor 'm-copy div-copy "Edit Copy" (inc c-copy))
(make-menu-executor 'm-cut div-cut "Edit Cut" (inc c-cut))
(make-menu-executor 'm-paste div-paste "Edit Paste" (inc c-paste))
(send this connect-menu! 'm-start
(λ () (send this start-counter)))
(send this connect-menu! 'm-stop (displayln "page-loaded done")
(λ () (send this reset-counter))) )
(send this connect-menu! 'm-prefs
(λ () (send this prefs)))
(send this connect-menu! 'm-select-dir
(λ () (send this choose-dir)))
)
)|#
(begin (begin
(displayln "Yes this works!") (displayln "Yes this works!")

View File

@@ -1,18 +1,23 @@
#lang info #lang info
(define pkg-authors '(hnmdijkema)) (define pkg-authors '(hnmdijkema))
(define version "0.2.20") (define version "0.1.1")
(define license 'MIT) (define license 'MIT)
(define collection "racket-webview") (define collection "racket-webview")
(define pkg-desc "racket-webview - A Web Based GUI library, based on a Qt WebEngine backend") (define pkg-desc "racket-webview - A Web Based GUI library, based on a Qt WebEngine backend")
(define scribblings (define scribblings
'( '(
("scrbl/web-racket.scrbl" () (gui-library) "web-racket") ("scrbl/racket-webview-qt.scrbl" () (gui-library) "racket-webview-qt")
("scrbl/web-racket-version.scrbl" () (gui-library) "web-racket-version") ("scrbl/racket-webview.scrbl" () (gui-library) "racket-webview")
("scrbl/web-wire.scrbl" () (gui-library) "web-wire") ("scrbl/wv-context.scrbl" () (gui-library) "wv-context")
("scrbl/webui-wire-download.scrbl" () (gui-library) "webui-wire-download") ("scrbl/wv-element.scrbl" () (gui-library) "wv-element")
("scrbl/webui-wire-ipc.scrbl" () (gui-library) "webui-wire-ipc") ("scrbl/wv-input.scrbl" () (gui-library) "wv-input")
("scrbl/wv-settings.scrbl" () (gui-library) "wv-settings")
("scrbl/wv-window.scrbl" () (gui-library) "wv-window")
("scrbl/rgba.scrbl" () (gui-library) "rgba")
"scrbl/rktwebview-api.scrbl"
"scrbl/rktwebviewqt-internals.scrbl"
) )
) )
@@ -36,3 +41,4 @@
"scribble-lib" "scribble-lib"
"net-doc" "net-doc"
)) ))

3
js/boilerplate.css Normal file
View File

@@ -0,0 +1,3 @@
body {
font-family: sans-serif;
}

View File

@@ -2,7 +2,11 @@
if (window.rkt_event_queue === undefined) { window.rkt_event_queue = []; } if (window.rkt_event_queue === undefined) { window.rkt_event_queue = []; }
window.rkt_put_evt = function(evt) { window.rkt_put_evt = function(evt) {
evt.timestamp = Date.now();
window.rkt_event_queue.push(evt); window.rkt_event_queue.push(evt);
if (window.rkt_evt_frame_el) {
window.rkt_evt_frame_win.print();
}
}; };
window.rkt_event_info = function(e, id, evt) { window.rkt_event_info = function(e, id, evt) {

113
js/menu.css Normal file
View File

@@ -0,0 +1,113 @@
div.menubar {
display: flex;
align-items: center;
width: 100%;
top: 0;
left: 0;
height: 2em;
background: #e0e0e0;
border-bottom: 1px solid black;
margin-bottom: 2px;
}
div.menubar-item {
display: inline-block;
height: 100%;
align-content: center;
padding-left: 0.5em;
padding-right: 0.5em;
cursor: default;
}
div.menu, div.submenu {
display: flex;
flex-direction: column;
background: #e0e0e0;
border: 1px solid black;
z-index: 9999;
margin-top: 0.4em;
margin-left: -0.5em;
position: absolute;
}
div.submenu {
position: absolute;
left: calc(100% + 1em);
top: 1em;
}
div.menu-item {
min-width: 150px;
height: 2em;
padding-left: 0.5em;
align-content: center;
align-items: center;
display: flex;
cursor: default;
}
div.menu-item span.menu-icon {
width: 2em;
height: 2em;
padding: 0;
margin: 0;
display: flex;
padding-left: 3px;
}
div.menu-item span.menu-name {
padding-left: 0.25em;
display: inline-block;
padding-right: 0.5em;
}
div.menu-item span.menu-submenu {
float: right;
}
span.menu-icon img {
width: 85%;
height: 85%;
align-self: center;
}
div.menu-item.separator {
border-top: 1px solid black;
}
div.menubar-item:hover, div.menu-item:hover {
background: #c0c0c0;;
}
.popup-menu, .popup-submenu {
display: flex;
flex-direction: column;
margin: 5px;
padding: 5px;
position: absolute;
z-index: 9999;
border: 1px solid black;
background: #e0e0e0;
color: black;
}
.popup-submenu {
display: none;
}
.menubar .menu-item span.menu-icon, .popup-menu .menu-item span.menu-icon {
min-width: unset;
width: unset;
}
.menubar .menu-item {
min-width: unset;
width: unset;
color: black;
}
.menu-item span.menu-name {
text-wrap: nowrap;
}

132
js/menu.js Normal file
View File

@@ -0,0 +1,132 @@
window._web_wire_popup_menu = function(menu, x = -1, y = -1, kind = 'popup') {
if (menu.id == '#f') { menu.id = null; }
let menu_id = (kind == 'popup') ? '@@popup-menu@@' : '@@menubar@@';
let submenu_els = [];
let triggerMenuItem;
let clearPopupMenu = function() {
if (kind == 'popup') {
let el = document.getElementById(menu_id);
if (el !== null) {
el.innerHTML = '';
el.style.display = 'none';
}
if (menu.id !== null) {
// Delay this trigger, because one could have choosen a menu item and we want this
// to be triggered before the clear command is send.
// But if no menu item has been selected, the clear command should
// eventually be send.
setTimeout(function () {
console.log("Sending clear trigger for menu clearance : " + menu.id);
let obj = { evt: 'menu-item-choosen', id: menu.id, menu_item: menu.id };
window.rkt_put_evt(obj);
}, 250);
}
} else {
// hide all submenus
submenu_els.forEach(function (el) { el.style.display = 'none'; });
}
};
triggerMenuItem = function(id) {
console.log("Triggering menu item : " + id);
let obj = { evt: 'menu-item-choosen', id: id, menu_item: id };
window.rkt_put_evt(obj);
};
let showSubMenu = function(menu_el, item_el, el, parent_type) {
if (parent_type == 'menu') {
el.style.display = 'flex';
let rect = item_el.getBoundingClientRect();
let r = rect.left;
let t = rect.height;
el.style.left = r + 'px';
el.style.top = t + 'px';
} else {
el.style.display = "flex";
let rect = menu_el.getBoundingClientRect();
let irect =item_el.getBoundingClientRect();
let r = rect.width + 5;
let t = irect.y - rect.y;
el.style.left = r + "px";
el.style.top = t + "px";
}
};
let hideSubMenu = function(el) { el.style.display = "none"; };
let makePopupMenu = function(el, menu, visible, type) {
let i;
let N = menu.length;
for(i = 0; i < N; i++) {
let item = menu[i];
let item_el = document.createElement("div");
item_el.id = item.id;
item_el.classList.add("menu-item");
let item_el_icon = document.createElement('span');
item_el_icon.classList.add("menu-icon");
if (item.icon) {
let icon_img = document.createElement('img');
icon_img.setAttribute('src', item.icon);
item_el_icon.appendChild(icon_img);
}
if (item.separator) {
item_el.classList.add("separator");
}
let item_el_name = document.createElement('span');
item_el_name.classList.add('menu-name');
item_el_name.innerHTML = item.name;
let item_el_submenu = document.createElement('span');
item_el_submenu.classList.add('menu-submenu');
if (item.submenu) {
if (type == 'submenu' || kind == 'popup') {
item_el_submenu.innerHTML = '&gt;';
}
item_el.setAttribute('type', 'submenu');
let submenu_el = document.createElement("div");
submenu_els.push(submenu_el);
submenu_el.classList.add("submenu");
submenu_el.classList.add("menu");
item_el.appendChild(submenu_el);
submenu_el.style.display = 'none';
makePopupMenu(submenu_el, item.submenu.menu, false, 'submenu');
item_el.addEventListener('mouseenter', function () { showSubMenu(el, item_el, submenu_el, type); });
item_el.addEventListener('mouseleave', function () { hideSubMenu(submenu_el); });
} else {
item_el.setAttribute('type', 'item');
item_el.addEventListener('click', function() { triggerMenuItem(item.id); });
}
item_el.appendChild(item_el_icon);
item_el.appendChild(item_el_name);
item_el.appendChild(item_el_submenu);
el.appendChild(item_el);
}
};
let el = document.getElementById(menu_id);
if (el === null) {
el = document.createElement("div");
el.id = menu_id;
el.classList.add((kind == 'popup') ? "popup-menu" : "menubar");
if (kind == 'popup') {
el.classList.add("menu");
document.body.appendChild(el);
} else {
document.body.prepend(el);
}
} else {
el.innerHTML = '';
}
makePopupMenu(el, menu.menu, true, 'menu');
el.style.left = x + "px";
el.style.top = y + "px";
el.style.display = "flex";
let clearer_f = function() {
clearPopupMenu();
document.body.removeEventListener('click', clearer_f);
document.body.removeEventListener('contextmenu', clearer_f);
};
document.body.addEventListener('click', clearer_f);
document.body.addEventListener('contextmenu', clearer_f);
};
window._web_wire_menu = function(_menubar) {
let menubar = JSON.parse(_menubar);
window._web_wire_popup_menu(menubar, -1, -1, 'menubar');
};

View File

@@ -6,12 +6,18 @@
(require "private/wv-dialog.rkt") (require "private/wv-dialog.rkt")
(require "private/wv-element.rkt") (require "private/wv-element.rkt")
(require "private/wv-input.rkt") (require "private/wv-input.rkt")
(require "private/rgba.rkt")
(require "private/mimetypes.rkt")
(require "private/menu.rkt")
(provide (all-from-out "private/wv-context.rkt" (provide (all-from-out "private/wv-context.rkt"
"private/wv-window.rkt" "private/wv-window.rkt"
"private/wv-dialog.rkt" "private/wv-dialog.rkt"
"private/wv-element.rkt" "private/wv-element.rkt"
"private/wv-input.rkt" "private/wv-input.rkt"
"private/rgba.rkt"
"private/mimetypes.rkt"
"private/menu.rkt"
) )
webview-set-loglevel webview-set-loglevel
webview-version webview-version

View File

@@ -1,60 +1,72 @@
(module menu racket/base (module menu racket/base
(require json) (require json
net/url)
(provide menu (provide wv-menu
menu-item wv-menu-item
is-menu? is-wv-menu?
menu-set-callback! wv-menu-set-callback!
menu-set-icon! wv-menu-set-icon!
menu-set-title! wv-menu-set-title!
menu->json wv-menu->json
with-menu-item with-wv-menu-item
menu-for-each wv-menu-for-each
ww-menu-item-callback wv-menu-item-callback
ww-menu-item-id wv-menu-item-id
ww-menu-id wv-menu-id
) )
(define-struct ww-menu-item
(id [title #:mutable] [icon-file #:mutable] [callback #:mutable] [submenu #:mutable] [separator #:mutable]) (define-struct ww-menu-item*
(id [title #:mutable] [icon-url #:mutable] [callback #:mutable] [submenu #:mutable] [separator #:mutable])
#:transparent) #:transparent)
(define-struct ww-menu (define-struct ww-menu*
(id [items #:mutable]) (id [items #:mutable])
#:transparent #:transparent
) )
(define (is-menu? mnu)
(if (ww-menu? mnu) (define (wv-menu-item-callback mi)
(if (list? (ww-menu-items mnu)) (ww-menu-item*-callback mi))
(define (wv-menu-item-id mi)
(ww-menu-item*-id mi))
(define (wv-menu-id m)
(ww-menu*-id m))
(define (is-wv-menu? mnu)
(if (ww-menu*? mnu)
(if (list? (ww-menu*-items mnu))
(letrec ((f (lambda (m) (letrec ((f (lambda (m)
(if (null? m) (if (null? m)
#t #t
(if (ww-menu-item? (car m)) (if (ww-menu-item*? (car m))
(if (eq? (ww-menu-item-submenu (car m)) #f) (if (eq? (ww-menu-item*-submenu (car m)) #f)
(f (cdr m)) (f (cdr m))
(and (is-menu? (ww-menu-item-submenu (car m))) (and (is-wv-menu? (ww-menu-item*-submenu (car m)))
(f (cdr m)))) (f (cdr m))))
#f) #f)
)) ))
)) ))
(f (ww-menu-items mnu))) (f (ww-menu*-items mnu)))
#f) #f)
#f)) #f))
(define (menu . items) (define (wv-menu . items)
(let ((menu-id #f)) (let ((menu-id #f))
(when (symbol? (car items)) (when (symbol? (car items))
(set! menu-id (car items)) (set! menu-id (car items))
(set! items (cdr items))) (set! items (cdr items)))
(when (list? (car items)) (when (list? (car items))
(set! items (car items))) (set! items (car items)))
(make-ww-menu menu-id items))) (make-ww-menu* menu-id items)))
(define (menu-item id title (define (wv-menu-item id title
#:icon-file [icon-file #f] #:icon-url [icon-url #f]
#:callback [callback (lambda args #t)] #:callback [callback (lambda args #t)]
#:submenu [submenu #f] #:submenu [submenu #f]
#:separator [separator #f]) #:separator [separator #f])
@@ -62,47 +74,49 @@
(error "menu-item needs an id of symbol?")) (error "menu-item needs an id of symbol?"))
(unless (string? title) (unless (string? title)
(error "menu-item needs a title of string?")) (error "menu-item needs a title of string?"))
(unless (or (eq? icon-file #f) (string? icon-file) (path? icon-file)) (unless (or (eq? icon-url #f) (string? icon-url) (url? icon-url))
(error "menu-item's optional argument icon-file must be #f, string? or path?")) (error "menu-item's optional argument icon-file must be #f, string? or path?"))
(unless (or (eq? submenu #f) (is-menu? submenu)) (unless (or (eq? submenu #f) (is-wv-menu? submenu))
(error "menu-item's optional argument submenu must be #f or is-menu?")) (error "menu-item's optional argument submenu must be #f or is-menu?"))
(unless (boolean? separator) (unless (boolean? separator)
(error "menu-item's optional argument separator must be boolean?")) (error "menu-item's optional argument separator must be boolean?"))
(make-ww-menu-item id title icon-file callback submenu separator)) (let ((u (if (url? icon-url) (url->string icon-url) icon-url)))
(make-ww-menu-item* id title u callback submenu separator))
)
(define (menu->hash menu . for-json) (define (wv-menu->hash menu . for-json)
(let ((fj (if (null? for-json) #f (car for-json)))) (let ((fj (if (null? for-json) #f (car for-json))))
(unless (is-menu? menu) (unless (is-wv-menu? menu)
(error "menu->hash must be called with a menu")) (error "menu->hash must be called with a menu"))
(let* ((items (ww-menu-items menu)) (let* ((items (ww-menu*-items menu))
(r (map (λ (item) (r (map (λ (item)
(let ((h (make-hasheq))) (let ((h (make-hasheq)))
(hash-set! h 'id (format "~a" (ww-menu-item-id item))) (hash-set! h 'id (format "~a" (ww-menu-item*-id item)))
(hash-set! h 'name (ww-menu-item-title item)) (hash-set! h 'name (ww-menu-item*-title item))
(unless (eq? (ww-menu-item-icon-file item) #f) (unless (eq? (ww-menu-item*-icon-url item) #f)
(hash-set! h 'icon (ww-menu-item-icon-file item))) (hash-set! h 'icon (ww-menu-item*-icon-url item)))
(unless (eq? (ww-menu-item-submenu item) #f) (unless (eq? (ww-menu-item*-submenu item) #f)
(hash-set! h 'submenu (menu->hash (ww-menu-item-submenu item) fj))) (hash-set! h 'submenu (wv-menu->hash (ww-menu-item*-submenu item) fj)))
(unless (eq? (ww-menu-item-separator item) #f) (unless (eq? (ww-menu-item*-separator item) #f)
(hash-set! h 'separator #t)) (hash-set! h 'separator #t))
h h
)) items)) )) items))
) )
(let ((h (make-hasheq))) (let ((h (make-hasheq)))
(hash-set! h 'menu r) (hash-set! h 'menu r)
(hash-set! h 'id (if fj (format "~a" (ww-menu-id menu)) (ww-menu-id menu))) (hash-set! h 'id (if fj (format "~a" (ww-menu*-id menu)) (ww-menu*-id menu)))
h)))) h))))
(define (menu-for-each menu cb) (define (wv-menu-for-each menu cb)
(let ((items (ww-menu-items menu))) (let ((items (ww-menu*-items menu)))
(letrec ((f (λ (items) (letrec ((f (λ (items)
(if (null? items) (if (null? items)
#t #t
(let ((item (car items))) (let ((item (car items)))
(let ((submenu (ww-menu-item-submenu item))) (let ((submenu (ww-menu-item*-submenu item)))
(if (eq? submenu #f) (if (eq? submenu #f)
(cb item) (cb item)
(menu-for-each submenu cb))) (wv-menu-for-each submenu cb)))
(f (cdr items)) (f (cdr items))
) )
) )
@@ -110,23 +124,23 @@
)) ))
(f items)))) (f items))))
(define (menu->json menu) (define (wv-menu->json menu)
(let ((o (open-output-string))) (let ((o (open-output-string)))
(write-json (menu->hash menu #t) o) (write-json (wv-menu->hash menu #t) o)
(get-output-string o))) (get-output-string o)))
(define (find-menu-item menu id) (define (find-wv-menu-item menu id)
(let ((items (ww-menu-items menu))) (let ((items (ww-menu*-items menu)))
(letrec ((f (λ (items) (letrec ((f (λ (items)
(if (null? items) (if (null? items)
#f #f
(let ((item (car items))) (let ((item (car items)))
(if (eq? (ww-menu-item-id item) id) (if (eq? (ww-menu-item*-id item) id)
item item
(let ((submenu (ww-menu-item-submenu item))) (let ((submenu (ww-menu-item*-submenu item)))
(if (eq? submenu #f) (if (eq? submenu #f)
(f (cdr items)) (f (cdr items))
(let ((found-item (find-menu-item submenu id))) (let ((found-item (find-wv-menu-item submenu id)))
(if (eq? found-item #f) (if (eq? found-item #f)
(f (cdr items)) (f (cdr items))
found-item)) found-item))
@@ -136,37 +150,38 @@
)) ))
(f items)))) (f items))))
(define (with-menu-item menu id cb) (define (with-wv-menu-item menu id cb)
(unless (is-menu? menu) (unless (is-wv-menu? menu)
(error "menu must be of is-menu?")) (error "menu must be of is-menu?"))
(unless (symbol? id) (unless (symbol? id)
(error "id must be of symbol?")) (error "id must be of symbol?"))
(let ((item (find-menu-item menu id))) (let ((item (find-wv-menu-item menu id)))
(if (eq? item #f) (if (eq? item #f)
(error (format "cannot find id'~a in given menu" id)) (error (format "cannot find id'~a in given menu" id))
(cb item))) (cb item)))
menu) menu)
(define (menu-set-title! menu id title) (define (wv-menu-set-title! menu id title)
(unless (string? title) (unless (string? title)
(error "title must be of string?")) (error "title must be of string?"))
(with-menu-item menu id (with-wv-menu-item menu id
(λ (item) (λ (item)
(set-ww-menu-item-title! item title)))) (set-ww-menu-item*-title! item title))))
(define (menu-set-icon! menu id icon) (define (wv-menu-set-icon! menu id icon-url)
(unless (or (eq? icon #f) (path? icon) (string? icon)) (unless (or (eq? icon-url #f) (url? icon-url) (string? icon-url))
(error "title must be of #f, string? or path?")) (error "title must be of #f, string? or path?"))
(with-menu-item menu id (with-wv-menu-item menu id
(λ (item) (λ (item)
(set-ww-menu-item-icon-file! item icon)))) (let ((u (if (url? icon-url) (url->string icon-url) icon-url)))
(set-ww-menu-item*-icon-url! item u)))))
(define (menu-set-callback! menu id cb) (define (wv-menu-set-callback! menu id cb)
(unless (procedure? cb) (unless (procedure? cb)
(error "callback must be of procedure?")) (error "callback must be of procedure?"))
(with-menu-item menu id (with-wv-menu-item menu id
(λ (item) (λ (item)
(set-ww-menu-item-callback! item cb)))) (set-ww-menu-item*-callback! item cb))))
); end of module ); end of module

View File

@@ -590,7 +590,7 @@
; ))))) ; )))))
(let ((handle (make-rkt-wv wv evt-queue evt-callback #t close-callback))) (let ((handle (make-rkt-wv wv evt-queue evt-callback #t close-callback)))
(thread (λ () (thread (λ ()
(sleep 1) (sleep 0.01)
(letrec ((f (λ () (letrec ((f (λ ()
(let ((r (rkt-process-events handle))) (let ((r (rkt-process-events handle)))
(if (eq? r 'quit) (if (eq? r 'quit)

View File

@@ -7,5 +7,5 @@
(define webview-major 0) (define webview-major 0)
(define webview-minor 1) (define webview-minor 1)
(define webview-patch 0) (define webview-patch 1)

View File

@@ -5,6 +5,7 @@
"utils.rkt" "utils.rkt"
"mimetypes.rkt" "mimetypes.rkt"
"rgba.rkt" "rgba.rkt"
"menu.rkt"
finalizer finalizer
racket/async-channel racket/async-channel
web-server/http web-server/http
@@ -73,6 +74,8 @@
webview-set-html! webview-set-html!
webview-base-url webview-base-url
webview-set-menu!
webview-set-innerHTML! webview-set-innerHTML!
webview-set-value! webview-set-value!
@@ -105,6 +108,7 @@
webview-standard-file-getter webview-standard-file-getter
webview-default-boilerplate-js webview-default-boilerplate-js
webview-default-boilerplate-css
webview-version webview-version
webview-info webview-info
@@ -123,16 +127,38 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-runtime-path js-path "../js") (define-runtime-path js-path "../js")
(define-runtime-path css-path "../js")
(define (webview-default-boilerplate-js . custom-js) (define (webview-default-boilerplate-js . custom-js)
(let ((file (build-path js-path "boilerplate.js"))) (let ((file (build-path js-path "boilerplate.js"))
(let ((bjs (file->string file))) (menu-js-file (build-path js-path "menu.js")))
(let ((bjs (file->string file))
(mjs (file->string menu-js-file))
)
(let ((js (string-append bjs (let ((js (string-append bjs
mjs
(if (null? custom-js) (if (null? custom-js)
"" ""
((car custom-js)))))) ((car custom-js))))))
js)))) js))))
(define (webview-default-boilerplate-css . custom-css)
(let ((file (build-path css-path "boilerplate.css"))
(menu-css-file (build-path css-path "menu.css")))
(let ((bcss (file->string file))
(mcss (file->string menu-css-file))
)
(let ((css (string-append bcss
mcss
(if (null? custom-css)
""
((car custom-css)))
)
)
)
css))))
(define-struct wv-context (define-struct wv-context
([context #:mutable] ([context #:mutable]
[port #:mutable] [port #:mutable]
@@ -142,6 +168,7 @@
[request-count #:mutable] [request-count #:mutable]
[sec-token-cache #:mutable] [sec-token-cache #:mutable]
[cert-ou-token #:mutable] [cert-ou-token #:mutable]
[boilerplate-css #:mutable]
) )
#:transparent #:transparent
) )
@@ -153,16 +180,16 @@
) )
#:transparent) #:transparent)
(define re_head #px"[<][/][Hh][eE][aA][dD][>]")
(define (process-html context path out) (define (process-html context path out)
(let ((html (file->string path))) (let ((html (file->string path)))
(display html out))) (let ((html* (regexp-replace re_head html
; (boilerplate-js ((wv-context-boilerplate-js wv-win-handle)))) (string-append "<style>\n"
; (set! html (string-replace html "<head>" (wv-context-boilerplate-css context)
; (string-append "<head>" "\n" "\n</style>\n"
; "<script>" "\n" "</head>"))))
; boilerplate-js "\n" (display html* out))))
; "</script>" "\n")))
; (display html out)))
(define (process-file context ext path out) (define (process-file context ext path out)
(let ((content (file->bytes path))) (let ((content (file->bytes path)))
@@ -414,11 +441,13 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define/contract (webview-new-context file-getter (define/contract (webview-new-context file-getter
#:boilerplate-js [bj (webview-default-boilerplate-js)]) #:boilerplate-js [bj (webview-default-boilerplate-js)]
(->* (procedure?) (#:boilerplate-js string?) wv-context?) #:boilerplate-css [bc (webview-default-boilerplate-css)])
(->* (procedure?) (#:boilerplate-js string? #:boilerplate-css string?) wv-context?)
(let* ((h (make-wv-context 0 0 file-getter #f #f 0 (let* ((h (make-wv-context 0 0 file-getter #f #f 0
(make-lru 250 #:cmp eq?) (make-lru 250 #:cmp eq?)
(symbol->string (make-security-token)) (symbol->string (make-security-token))
bc
)) ))
(cert (generate-self-signed-cert 2048 365 '("127.0.0.1" "localhost") (cert (generate-self-signed-cert 2048 365 '("127.0.0.1" "localhost")
"NL" "Dijkema" "NL" "Dijkema"
@@ -497,6 +526,17 @@
) )
) )
(define/contract (webview-set-menu! wv menu)
(-> wv-win? is-wv-menu? symbol?)
(let* ((json (wv-menu->json menu))
(js (string-append "window._web_wire_menu('"
json
"');"))
)
(webview-run-js wv js)
)
)
(define (loglevel? x) (define (loglevel? x)
(and (symbol? x) (and (symbol? x)
(or (eq? x 'error) (eq? x 'info) (eq? x 'debug) (eq? x 'warning)))) (or (eq? x 'error) (eq? x 'info) (eq? x 'debug) (eq? x 'warning))))

View File

@@ -13,7 +13,9 @@
base-path base-path
[file-getter (webview-standard-file-getter base-path)] [file-getter (webview-standard-file-getter base-path)]
[context-js (λ () "")] [context-js (λ () "")]
[context-css (λ () "")]
[boilerplate-js (webview-default-boilerplate-js context-js)] [boilerplate-js (webview-default-boilerplate-js context-js)]
[boilerplate-css (webview-default-boilerplate-css context-css)]
[ini (error "You need to provide a 'ini' file settings interface for settings, e.g. simple-ini/class")] [ini (error "You need to provide a 'ini' file settings interface for settings, e.g. simple-ini/class")]
) )
@@ -35,7 +37,8 @@
(begin (begin
(set! wv-context (set! wv-context
(webview-new-context file-getter (webview-new-context file-getter
#:boilerplate-js boilerplate-js)) #:boilerplate-js boilerplate-js
#:boilerplate-css boilerplate-css))
(set! settings-obj (new wv-settings% [ini ini] [wv-context 'global])) (set! settings-obj (new wv-settings% [ini ini] [wv-context 'global]))
) )
) )

View File

@@ -38,9 +38,9 @@
(let ((x (inexact->exact (round (exact->inexact (+ px xx))))) (let ((x (inexact->exact (round (exact->inexact (+ px xx)))))
(y (inexact->exact (round (exact->inexact (+ py yy))))) (y (inexact->exact (round (exact->inexact (+ py yy)))))
) )
(displayln "move") (displayln (format "move ~a ~a" x y))
(send this move x y) (send this move x y)
(displayln "resize") (displayln (format "resize ~a ~a" x y))
(send this resize dw dh) (send this resize dw dh)
) )
) )

View File

@@ -145,6 +145,7 @@
(define (event-handler wv evt) (define (event-handler wv evt)
(let ((event (hash-ref evt 'event 'unknown-event)) (let ((event (hash-ref evt 'event 'unknown-event))
) )
(displayln evt)
(cond (cond
((eq? event 'resize) ((eq? event 'resize)
(send this resized (hash-ref evt 'w) (hash-ref evt 'h))) (send this resized (hash-ref evt 'w) (hash-ref evt 'h)))
@@ -163,7 +164,7 @@
(let* ((je (hash-copy (hash-ref evt 'js-evt))) (let* ((je (hash-copy (hash-ref evt 'js-evt)))
(e (make-hash))) (e (make-hash)))
(hash-set! e 'evt (string->symbol (hash-ref je 'evt))) (hash-set! e 'evt (string->symbol (hash-ref je 'evt)))
(hash-set! e 'id (string->symbol (hash-ref je 'id #f))) (hash-set! e 'id (string->symbol (hash-ref je 'id "nil")))
(hash-set! e 'data (hash-ref je 'js_evt (make-hash))) (hash-set! e 'data (hash-ref je 'js_evt (make-hash)))
(hash-set! e 'event 'js-evt) (hash-set! e 'event 'js-evt)
(when (eq? (send this js-event e) 'wv-unhandled-js-event) (when (eq? (send this js-event e) 'wv-unhandled-js-event)
@@ -245,7 +246,6 @@
) )
) )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Commands ;; Commands
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -258,6 +258,15 @@
(webview-remove-class! wv selector-or-id cl) (webview-remove-class! wv selector-or-id cl)
this) this)
(define/public (set-menu! menu)
(webview-set-menu! wv menu)
this)
(define/public (connect-menu! id callback)
(send this bind! (string->symbol (format "~a" id)) 'menu-item-choosen
(λ (el evt data)
(callback))))
(define/public (devtools) (define/public (devtools)
(webview-devtools wv) (webview-devtools wv)
this) this)

View File

@@ -27,3 +27,4 @@ QHash<QString, QVariant> mkEvent()
} }
int EventContainer::evt_count = 0; int EventContainer::evt_count = 0;
qint64 EventContainer::ms_start = -1;

View File

@@ -4,15 +4,24 @@
#include <QHash> #include <QHash>
#include <QString> #include <QString>
#include <QVariant> #include <QVariant>
#include <QDateTime>
class EventContainer : public QHash<QString, QVariant> class EventContainer : public QHash<QString, QVariant>
{ {
private: private:
static int evt_count; static int evt_count;
static qint64 ms_start;
public: public:
EventContainer(const QString &evt) { EventContainer(const QString &evt) {
if (ms_start < 0) {
ms_start = QDateTime::currentMSecsSinceEpoch();
}
this->insert("event", evt); this->insert("event", evt);
this->insert("evt-id", ++evt_count); this->insert("evt-id", ++evt_count);
qint64 ms = QDateTime::currentMSecsSinceEpoch();
this->insert("timestamp", ms);
this->insert("elaped", static_cast<double>(ms - ms_start) / 1000.0);
} }
}; };

View File

@@ -59,6 +59,7 @@ void Rktwebview_qt::processCommand(Command *cmd)
"window.rkt_evt_frame_el = null;\n" "window.rkt_evt_frame_el = null;\n"
"window.rkt_evt_frame_win = null;\n" "window.rkt_evt_frame_win = null;\n"
"window.rkt_send_event = function(obj) {\n" "window.rkt_send_event = function(obj) {\n"
" obj.timestamp = Date.now();\n"
" //console.log('Sending event: ' + obj);\n" " //console.log('Sending event: ' + obj);\n"
" window.rkt_event_queue.push(obj);\n" " window.rkt_event_queue.push(obj);\n"
" if (window.rkt_evt_frame_el) {\n" " if (window.rkt_evt_frame_el) {\n"

195
scrbl/menu.scrbl Normal file
View File

@@ -0,0 +1,195 @@
#lang scribble/manual
@(require racket/base
scribble/core
(for-label racket/base
racket/string
net/url
json
"../private/menu.rkt"))
@title{menu}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
@defmodule[menu]
Menu data structures used by the webview library.
This module provides constructors, predicates, traversal helpers, mutation
operations, and JSON conversion for menu trees.
@section{Overview}
A menu is represented as a tree. A menu consists of menu items, and a menu item
may optionally contain a submenu.
Menu identifiers are symbols. Menu item titles are strings. Icons are stored as
strings and may be supplied either as @racket[#f], strings, or URL values.
The module does not display menus itself. It provides the menu data structure
used by higher layers.
@section{Internal Representation}
Internally, menus are represented by transparent structure values. These
structure constructors and predicates are not exported directly. The public API
uses constructor procedures and helper functions operating on those internal
values.
@section{Predicates}
@defproc[(is-wv-menu? [mnu any/c]) boolean?]{
Returns @racket[#t] if @racket[mnu] is a valid menu tree and @racket[#f]
otherwise.
A value is recognized as a menu if it is an internal menu structure whose item
list is a list of internal menu items, and every submenu recursively also
satisfies @racket[is-wv-menu?].
}
@section{Constructors}
@defproc[(wv-menu [item-or-id any/c] ...)
any/c]{
Creates a menu.
If the first argument is a symbol, it is used as the menu identifier and removed
from the remaining arguments. Otherwise the menu identifier is @racket[#f].
If the first remaining argument is itself a list, that list is used as the item
list. Otherwise all remaining arguments are treated as menu items.
This means the following forms are accepted:
@racketblock[
(wv-menu item ...)
(wv-menu 'some-id item ...)
(wv-menu (list item ...))
(wv-menu 'some-id (list item ...))
]
The result is a value satisfying @racket[is-wv-menu?].
}
@defproc[(wv-menu-item [id symbol?]
[title string?]
[#:icon-url icon-url (or/c boolean? string? url?) #f]
[#:callback callback procedure? (λ args #t)]
[#:submenu submenu (or/c boolean? any/c) #f]
[#:separator separator boolean? #f])
any/c]{
Creates a menu item.
@racket[id] must be a symbol and @racket[title] must be a string.
@racket[icon-url] must be @racket[#f], a string, or a URL value. If it is a URL
value, it is converted to a string using @racket[url->string] before being
stored.
@racket[submenu] must be @racket[#f] or a value satisfying
@racket[is-wv-menu?].
@racket[separator] must be a boolean.
If any argument does not satisfy these conditions, an exception is raised.
}
@section{Traversal and Lookup}
@defproc[(wv-menu-for-each [menu any/c] [cb procedure?]) boolean?]{
Traverses @racket[menu] depth-first and calls @racket[cb] for each menu item.
If a menu item contains a submenu, that submenu is traversed recursively.
The callback is invoked only for menu items that are reached by the traversal.
The function returns @racket[#t].
}
@defproc[(with-wv-menu-item [menu any/c] [id symbol?] [cb procedure?]) any/c]{
Finds the menu item identified by @racket[id] and applies @racket[cb] to it.
If @racket[menu] does not satisfy @racket[is-wv-menu?], an exception is raised.
If @racket[id] is not a symbol, an exception is raised.
If no item with the given id can be found, an exception is raised.
After the callback has been applied, the original @racket[menu] value is
returned.
}
@section{Mutation}
@defproc[(wv-menu-set-title! [menu any/c] [id symbol?] [title string?])
any/c]{
Sets the title of the menu item identified by @racket[id].
@racket[title] must be a string. The function returns the original
@racket[menu] value.
}
@defproc[(wv-menu-set-icon! [menu any/c] [id symbol?]
[icon-url (or/c boolean? string? url?)])
any/c]{
Sets the icon URL of the menu item identified by @racket[id].
@racket[icon-url] must be @racket[#f], a string, or a URL value. If it is a URL
value, it is converted to a string using @racket[url->string] before being
stored.
The function returns the original @racket[menu] value.
}
@defproc[(wv-menu-set-callback! [menu any/c] [id symbol?] [cb procedure?])
any/c]{
Sets the callback of the menu item identified by @racket[id].
@racket[cb] must be a procedure. The function returns the original
@racket[menu] value.
}
@section{Conversion}
@defproc[(wv-menu->json [menu any/c]) string?]{
Converts @racket[menu] to a JSON string.
The conversion first builds a hash-based representation of the menu and then
writes that representation with @racket[write-json].
In the JSON representation:
@itemlist[#:style 'compact
@item{the top-level object contains the keys @racket['menu] and @racket['id]}
@item{menu item identifiers are converted to strings}
@item{menu item titles are written under the key @racket['name]}
@item{an icon is written only if it is not @racket[#f]}
@item{a submenu is written recursively only if it is not @racket[#f]}
@item{a separator flag is written only if it is not @racket[#f]}]
The @racket['id] field of the top-level menu is also converted to a string in
the JSON output.
}
@section{Accessors}
@defproc[(wv-menu-id [m any/c]) any/c]{
Returns the identifier of @racket[m].
}
@defproc[(wv-menu-item-id [mi any/c]) symbol?]{
Returns the identifier of the menu item @racket[mi].
}
@defproc[(wv-menu-item-callback [mi any/c]) procedure?]{
Returns the callback associated with the menu item @racket[mi].
}

60
scrbl/mimetypes.scrbl Normal file
View File

@@ -0,0 +1,60 @@
#lang scribble/manual
@(require racket/base
scribble/core
(for-label racket/base
racket/string
racket/file))
@title{mimetypes}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
@defmodule[mimetypes]
MIME type utilities used by the webview library.
This module provides a mapping from file extensions to MIME types together with
a function to determine the MIME type of a file path.
@section{Overview}
The module is used by the local HTTPS server to determine the correct
@tt{Content-Type} header when serving files.
MIME types are determined based on the file extension.
@section{Function: path->mimetype}
@defproc[(path->mimetype [p path-string?]) string?]{
Returns the MIME type associated with @racket[p].
The file extension of @racket[p] is extracted and used to look up the
corresponding MIME type.
If the extension is not recognized, a default MIME type is returned.
}
@section{Mapping}
The module contains a predefined mapping from file extensions to MIME types.
Typical mappings include:
@itemlist[#:style 'compact
@item{@tt{.html} → @tt{text/html}}
@item{@tt{.css} → @tt{text/css}}
@item{@tt{.js} → @tt{application/javascript}}
@item{@tt{.json} → @tt{application/json}}
@item{@tt{.png} → @tt{image/png}}
@item{@tt{.jpg}, @tt{.jpeg} → @tt{image/jpeg}}
@item{@tt{.svg} → @tt{image/svg+xml}}]
The exact mapping is defined in the source code and can be extended if needed.
@section{Notes}
The returned MIME type is always a string suitable for use in HTTP response
headers.
File extensions are interpreted case-insensitively.

View File

@@ -0,0 +1,64 @@
#lang scribble/manual
@(require racket/base
scribble/core
scribble/manual
(for-label racket/base
racket/class
racket/string
racket/file
net/url
"../private/wv-context.rkt"
"../private/wv-window.rkt"
"../private/wv-element.rkt"
"../private/wv-input.rkt"
"../private/wv-dialog.rkt"
"../private/wv-settings.rkt"
"../private/rgba.rkt"
"../private/mimetypes.rkt"))
@title{Racket Webview}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
@section{Overview}
Racket Webview is a class-oriented webview library built on top of a Qt-based
native runtime.
The library is layered. At the lowest level a native FFI layer is used. On top
of that, @racketmodname[racket-webview] provides a functional API. The
class-oriented API described in this manual is built from smaller modules on top
of that layer.
@section{Core Concepts}
The library is structured around two main concepts:
@itemlist[#:style 'compact
@item{a context, representing an isolated runtime with its own local HTTPS server}
@item{a window, representing a webview instance within such a context}]
A context manages local file serving, certificates, and settings. A window loads
content, handles events, and provides access to DOM elements.
@section{Modules}
The public API is divided into the following modules:
@itemlist[#:style 'compact
@item{@racketmodname[wv-context] — context creation and settings access}
@item{@racketmodname[wv-window] — window lifecycle, events, navigation, and dialogs}
@item{@racketmodname[wv-element] — DOM element wrapper}
@item{@racketmodname[wv-input] — typed input-element wrappers}
@item{@racketmodname[wv-dialog] — dialog windows}
@item{@racketmodname[wv-settings] — settings wrapper}
@item{@racketmodname[rgba] — RGBA color values}
@item{@racketmodname[mimetypes] — MIME type lookup}]
@section{Typical Usage}
A typical application creates a @racket[wv-context%] object and then creates one
or more @racket[wv-window%] objects within that context. DOM elements are
retrieved using @racket[(send window element 'id)], after which their state can
be read or modified using the element and input wrapper classes. Interaction
with the browser is handled through event callbacks and bindings.

View File

@@ -27,25 +27,38 @@ The module builds on the lower-level bindings from
A context encapsulates a native webview context together with a local HTTPS A context encapsulates a native webview context together with a local HTTPS
server. Windows are created within a context and communicate through events and server. Windows are created within a context and communicate through events and
JavaScript calls. JavaScript calls. When an HTML file is
served, the context's CSS boilerplate is injected immediately before the
closing @tt{</head>} tag.
@section{Contexts} @section{Contexts}
@defproc[(webview-new-context @defproc[(webview-new-context
[file-getter procedure?] [file-getter procedure?]
[#:boilerplate-js boilerplate-js string? [#:boilerplate-js boilerplate-js string?
(webview-default-boilerplate-js)]) (webview-default-boilerplate-js)]
[#:boilerplate-css boilerplate-css string?
(webview-default-boilerplate-css)])
wv-context?]{ wv-context?]{
Creates a new context. Creates a new context.
The function:
@itemlist[#:style 'compact @itemlist[#:style 'compact
@item{starts a local HTTPS server on a dynamically chosen port} @item{starts a local HTTPS server on a dynamically chosen port}
@item{generates a self-signed certificate} @item{generates a self-signed certificate}
@item{creates a native context} @item{creates a native context}
@item{installs the provided JavaScript boilerplate}] @item{installs the provided JavaScript boilerplate in the native context}
@item{stores the provided CSS boilerplate in the context}]
The @racket[file-getter] procedure maps request paths to files. The @racket[file-getter] procedure maps request paths to files. The
@racket[#:boilerplate-js] argument provides JavaScript support code passed to
the native context constructor. The javascript is injected by the native
QtWebEngine software. The @racket[#:boilerplate-css] argument provides
CSS boilerplate that is stored in the context and injected into served HTML
documents.
Certificate files are removed automatically when the context is garbage Certificate files are removed automatically when the context is garbage
collected. collected.
@@ -58,6 +71,7 @@ Returns the base URL of the context.
This URL can be used to construct URLs from relative path information. This URL can be used to construct URLs from relative path information.
} }
@defproc[(wv-context? [v any/c]) boolean?]{ @defproc[(wv-context? [v any/c]) boolean?]{
Recognizes context values. Recognizes context values.
} }
@@ -147,6 +161,17 @@ Replaces the current document contents.
X-expressions are converted to strings before being passed to JavaScript. X-expressions are converted to strings before being passed to JavaScript.
} }
@defproc[(webview-set-menu! [wv wv-win?] [menu is-wv-menu?])
symbol?]{
Installs @racket[menu] in @racket[wv].
The menu is converted to JSON using @racket[wv-menu->json] and then passed to
the browser by evaluating JavaScript through @racket[webview-run-js].
The result is the symbol returned by @racket[webview-run-js].
}
@defproc[(webview-set-title! [wv wv-win?] [title string?]) symbol?]{ @defproc[(webview-set-title! [wv wv-win?] [title string?]) symbol?]{
Sets the window title. Sets the window title.
} }
@@ -525,9 +550,23 @@ Recognizes lists of filter entries.
@section{Utilities} @section{Utilities}
@defproc[(webview-default-boilerplate-js [f procedure?] ...) @defproc[(webview-default-boilerplate-js [custom-js procedure?] ...)
string?]{ string?]{
Returns the default JavaScript boilerplate. Returns the default JavaScript boilerplate.
The result is constructed by concatenating the contents of @tt{js/*.js}.
If an additional procedure is supplied, its returned string is appended
to that JavaScript.
}
@defproc[(webview-default-boilerplate-css [custom-css procedure?] ...)
string?]{
Returns the default CSS boilerplate.
The result is constructed by concatenating the contents of @tt{js/*.css}.
If an additional procedure is supplied, its returned string is appended to that CSS.
} }
@defproc[(webview-standard-file-getter @defproc[(webview-standard-file-getter

96
scrbl/rgba.scrbl Normal file
View File

@@ -0,0 +1,96 @@
#lang scribble/manual
@(require racket/base
scribble/core
(for-label racket/base
racket/string
racket/contract))
@title{rgba}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
@defmodule[rgba]
RGBA color support used by the webview library.
This module exports a transparent @racket[rgba] structure together with
predicates and conversion procedures for working with CSS-style color values.
@section{Overview}
An @racket[rgba] value represents a color using red, green, blue, and alpha
components.
The module provides:
@itemlist[#:style 'compact
@item{the @racket[rgba] structure type}
@item{predicates for validating component values}
@item{conversion from strings to @racket[rgba] values}
@item{conversion from @racket[rgba] values to CSS color strings}]
The intended external representation is the CSS form:
@racketblock[
"rgba(r,g,b,a)"
]
@section{Predicates}
@defproc[(rgba/color? [v any/c]) boolean?]{
Recognizes valid red, green, and blue component values.
A valid color component is an exact integer in the range from @racket[0] to
@racket[255], inclusive.
}
@defproc[(rgba/alpha? [v any/c]) boolean?]{
Recognizes valid alpha component values.
A valid alpha component is a real number in the range from @racket[0] to
@racket[1], inclusive.
}
@section{Structure Type}
@defstruct*[rgba ([r rgba/color?]
[g rgba/color?]
[b rgba/color?]
[a rgba/alpha?])]{
Represents one RGBA color value.
The fields @racket[r], @racket[g], and @racket[b] are the red, green, and blue
components. The field @racket[a] is the alpha component.
The structure is transparent.
}
@section{Conversion}
@defproc[(rgba->string [c rgba?]) string?]{
Converts @racket[c] to a CSS color string.
The result has the form:
@racketblock[
"rgba(r,g,b,a)"
]
}
@defproc[(string->rgba [s string?]) (or/c rgba? #f)]{
Attempts to parse @racket[s] as a CSS RGBA color string.
If parsing succeeds, the result is an @racket[rgba] value. If parsing fails, the
result is @racket[#f].
The accepted input format is the one produced by @racket[rgba->string], namely:
@racketblock[
"rgba(r,g,b,a)"
]
}

View File

@@ -24,6 +24,10 @@ windows. It owns the underlying webview context, provides the base URL used by
those windows, and gives access to persistent settings through those windows, and gives access to persistent settings through
@racketmodname[wv-settings]. @racketmodname[wv-settings].
A context stores both JavaScript and CSS boilerplate. The JavaScript boilerplate
is passed to the native runtime, while the CSS boilerplate is injected into HTML
documents served by the local HTTPS server.
This module exports the @racket[wv-context%] class. This module exports the @racket[wv-context%] class.
@defclass[wv-context% object% ()]{ @defclass[wv-context% object% ()]{
@@ -40,10 +44,17 @@ to access that value, its base URL, and a settings object.
(webview-standard-file-getter base-path)] (webview-standard-file-getter base-path)]
[context-js procedure? [context-js procedure?
(λ () "")] (λ () "")]
[context-css procedure?
(λ () "")]
[boilerplate-js string? [boilerplate-js string?
(webview-default-boilerplate-js context-js)] (webview-default-boilerplate-js context-js)]
[boilerplate-css string?
(webview-default-boilerplate-css context-css)]
[ini any/c [ini any/c
(error "You need to provide a 'ini' file settings interface for settings, e.g. simple-ini/class")])]{ (error
(string-append "You need to provide a 'ini' "
"file settings interface for "
"settings, e.g. simple-ini/class"))])]{
Creates a new context object. Creates a new context object.
@@ -56,9 +67,14 @@ The constructor accepts the following initialization fields.
@racket[(webview-standard-file-getter base-path)].} @racket[(webview-standard-file-getter base-path)].}
@item{@racket[context-js] is a procedure producing additional JavaScript for @item{@racket[context-js] is a procedure producing additional JavaScript for
the context. Its default value is @racket[(λ () "")].} the context. Its default value is @racket[(λ () "")].}
@item{@racket[context-css] is a procedure producing additional CSS for the
context. Its default value is @racket[(λ () "")].}
@item{@racket[boilerplate-js] is the JavaScript boilerplate installed into the @item{@racket[boilerplate-js] is the JavaScript boilerplate installed into the
underlying webview context. Its default value is underlying webview context. Its default value is
@racket[(webview-default-boilerplate-js context-js)].} @racket[(webview-default-boilerplate-js context-js)].}
@item{@racket[boilerplate-css] is the CSS boilerplate stored in the context
and injected into HTML documents. Its default value is
@racket[(webview-default-boilerplate-css context-css)].}
@item{@racket[ini] is the settings backend used to construct the associated @item{@racket[ini] is the settings backend used to construct the associated
@racket[wv-settings%] object. No default backend is provided; omitting it @racket[wv-settings%] object. No default backend is provided; omitting it
raises an error.}] raises an error.}]

83
scrbl/wv-dialog.scrbl Normal file
View File

@@ -0,0 +1,83 @@
#lang scribble/manual
@(require racket/base
racket/class
scribble/core
(for-label racket/base
racket/class
"../private/wv-window.rkt"))
@title{wv-dialog}
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
@defmodule[wv-dialog]
Dialog-window wrapper built on top of @racket[wv-window%].
This module exports the @racket[wv-dialog%] class. It is a specialized window
class whose initial size and position are derived from its parent window.
@section{Overview}
A @racket[wv-dialog%] object is a @racket[wv-window%] that initializes itself as
a dialog relative to its parent window.
The class inherits the window lifecycle, event handling, navigation, and dialog
support from @racket[wv-window%]. Its only specialization in the current source
is the implementation of @racket[init-size].
@section{Class: wv-dialog%}
@defclass[wv-dialog% wv-window% ()]{
Represents a dialog window centered relative to its parent window.
The class inherits the fields @racket[parent], @racket[settings],
@racket[wv-context], @racket[html-path], @racket[x], @racket[y],
@racket[width], and @racket[height] from @racket[wv-window%].
@defconstructor[()]{
Creates a dialog window.
The constructor does not define additional initialization arguments of its own.
Construction is delegated to @racket[wv-window%] through @racket[super-new].
}
@defmethod[(init-size) any/c]{
Initializes the dialog size and position relative to its parent window.
The method reads the parent window geometry from the inherited @racket[parent]
field:
@itemlist[#:style 'compact
@item{@racket[x] and @racket[y] of the parent window}
@item{@racket[width] and @racket[height] of the parent window}]
It then determines the dialog width and height from @racket[settings], using the
keys @racket['width] and @racket['height]. If a stored value is absent, the
constructor fields @racket[width] and @racket[height] are used if present.
Otherwise both dimensions default to @racket[400].
The dialog position is then computed so that the dialog is centered within the
parent window:
@racketblock[
(let ((xx (/ (- pw dw) 2))
(yy (/ (- ph dh) 2)))
...)
]
The resulting coordinates are rounded, converted to exact integers, and applied
using:
@racketblock[
(send this move x y)
(send this resize dw dh)
]
This method overrides the inherited @racket[init-size] implementation from
@racket[wv-window%].
}
}

View File

@@ -83,7 +83,7 @@ If a parent is supplied, the underlying lower-level parent window is passed to
@racket[webview-create]. @racket[webview-create].
} }
@defmethod[(context) any/c]{ @defmethod[(context) (is-a?/c wv-context%)]{
Returns the window context object supplied at construction. Returns the window context object supplied at construction.
} }
@@ -266,6 +266,37 @@ If an element wrapper no longer has any event callbacks, it is removed from the
internal cache. internal cache.
The returned value is the list produced by @racket[webview-unbind!]. The returned value is the list produced by @racket[webview-unbind!].
}
@defmethod[(set-menu! [menu is-wv-menu?])
(is-a?/c wv-window%)]{
Installs @racket[menu] in this window and returns this window.
The accepted argument follows the contract of @racket[webview-set-menu!]. The
method delegates to:
@racketblock[
(webview-set-menu! wv menu)
]
}
@defmethod[(connect-menu! [id symbol?] [callback procedure?])
(is-a?/c wv-window%)]{
Connects @racket[callback] to the menu item identified by @racket[id].
The method installs a binding for the @racket['menu-item-choosen] event by
delegating to:
@racketblock[
(send this bind! id 'menu-item-choosen
(λ (el evt data)
(callback)))
]
The callback is invoked without arguments when the corresponding menu item is
chosen.
} }
@defmethod[(set-title! [title string?]) any/c]{ @defmethod[(set-title! [title string?]) any/c]{