documentation
This commit is contained in:
@@ -23,36 +23,34 @@
|
||||
(define-runtime-path dialog-html "example-1-dialog.html")
|
||||
(define-runtime-path cur-dir ".")
|
||||
|
||||
#|
|
||||
(define test-menu (menu 'main-menu
|
||||
(menu-item 'm-file "File"
|
||||
#:submenu
|
||||
(menu (menu-item 'm-open "Open File")
|
||||
(menu-item 'm-close "Close File")
|
||||
(menu-item 'm-select-dir "Select Folder" #:separator #t)
|
||||
(menu-item 'm-quit "Quit" #:separator #t)))
|
||||
(menu-item 'm-edit "Edit"
|
||||
#:submenu
|
||||
(menu (menu-item 'm-copy "Copy")
|
||||
(menu-item 'm-cut "Cut")
|
||||
(menu-item 'm-paste "Paste")
|
||||
(menu-item 'm-prefs "Preferences" #:separator #t)
|
||||
))
|
||||
(menu-item 'm-auto "Processes"
|
||||
#:submenu
|
||||
(menu (menu-item 'm-start "Start counter")
|
||||
(menu-item 'm-sub "Submenu"
|
||||
#:submenu
|
||||
(menu (menu-item 'm-sub1 "Submenu 1")
|
||||
(menu-item 'm-sub2 "Submenu 2")
|
||||
(menu-item 'm-sub3 "Submenu 3")
|
||||
)
|
||||
)
|
||||
(menu-item 'm-stop "Stop counter")
|
||||
)
|
||||
)
|
||||
))
|
||||
|#
|
||||
(define test-menu (wv-menu 'main-menu
|
||||
(wv-menu-item 'm-file "File"
|
||||
#:submenu
|
||||
(wv-menu (wv-menu-item 'm-open "Open File")
|
||||
(wv-menu-item 'm-close "Close File")
|
||||
(wv-menu-item 'm-select-dir "Select Folder" #:separator #t)
|
||||
(wv-menu-item 'm-quit "Quit" #:separator #t)))
|
||||
(wv-menu-item 'm-edit "Edit"
|
||||
#:submenu
|
||||
(wv-menu (wv-menu-item 'm-copy "Copy")
|
||||
(wv-menu-item 'm-cut "Cut")
|
||||
(wv-menu-item 'm-paste "Paste")
|
||||
(wv-menu-item 'm-prefs "Preferences" #:separator #t)
|
||||
))
|
||||
(wv-menu-item 'm-auto "Processes"
|
||||
#:submenu
|
||||
(wv-menu (wv-menu-item 'm-start "Start counter")
|
||||
(wv-menu-item 'm-sub "Submenu"
|
||||
#:submenu
|
||||
(wv-menu (wv-menu-item 'm-sub1 "Submenu 1")
|
||||
(wv-menu-item 'm-sub2 "Submenu 2")
|
||||
(wv-menu-item 'm-sub3 "Submenu 3")
|
||||
)
|
||||
)
|
||||
(wv-menu-item 'm-stop "Stop counter")
|
||||
)
|
||||
)
|
||||
))
|
||||
|
||||
(define example-1-dialog%
|
||||
(class wv-dialog%
|
||||
@@ -222,7 +220,7 @@
|
||||
)
|
||||
|
||||
(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)
|
||||
(super page-loaded oke)
|
||||
(displayln "super called")
|
||||
@@ -259,11 +257,13 @@
|
||||
(λ (el evt data)
|
||||
(send this choose-dir*)))
|
||||
)
|
||||
(displayln "page-loaded done")
|
||||
)
|
||||
|
||||
;(ww-debug "SETTING MENU")
|
||||
#|(let* ((div-open (send this element 'div-open))
|
||||
(displayln (format "setting menu ~a" (current-milliseconds)))
|
||||
(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)
|
||||
(div-close (send this element 'div-close))
|
||||
(c-close 0)
|
||||
@@ -273,39 +273,29 @@
|
||||
(c-cut 0)
|
||||
(div-paste (send this element 'div-paste))
|
||||
(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-quit
|
||||
(λ ()
|
||||
(send this reset-counter)
|
||||
(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 (λ () (send this reset-counter)))
|
||||
(send this connect-menu! 'm-prefs (λ () (send this prefs)))
|
||||
(send this connect-menu! 'm-select-dir (λ () (send this choose-dir*)))
|
||||
|
||||
(send this connect-menu! 'm-start
|
||||
(λ () (send this start-counter)))
|
||||
|
||||
(send this connect-menu! 'm-stop
|
||||
(λ () (send this reset-counter)))
|
||||
|
||||
(send this connect-menu! 'm-prefs
|
||||
(λ () (send this prefs)))
|
||||
|
||||
(send this connect-menu! 'm-select-dir
|
||||
(λ () (send this choose-dir)))
|
||||
|
||||
)
|
||||
)|#
|
||||
(displayln "page-loaded done")
|
||||
)
|
||||
|
||||
(begin
|
||||
(displayln "Yes this works!")
|
||||
|
||||
18
info.rkt
18
info.rkt
@@ -1,18 +1,23 @@
|
||||
#lang info
|
||||
|
||||
(define pkg-authors '(hnmdijkema))
|
||||
(define version "0.2.20")
|
||||
(define version "0.1.1")
|
||||
(define license 'MIT)
|
||||
(define collection "racket-webview")
|
||||
(define pkg-desc "racket-webview - A Web Based GUI library, based on a Qt WebEngine backend")
|
||||
|
||||
(define scribblings
|
||||
'(
|
||||
("scrbl/web-racket.scrbl" () (gui-library) "web-racket")
|
||||
("scrbl/web-racket-version.scrbl" () (gui-library) "web-racket-version")
|
||||
("scrbl/web-wire.scrbl" () (gui-library) "web-wire")
|
||||
("scrbl/webui-wire-download.scrbl" () (gui-library) "webui-wire-download")
|
||||
("scrbl/webui-wire-ipc.scrbl" () (gui-library) "webui-wire-ipc")
|
||||
("scrbl/racket-webview-qt.scrbl" () (gui-library) "racket-webview-qt")
|
||||
("scrbl/racket-webview.scrbl" () (gui-library) "racket-webview")
|
||||
("scrbl/wv-context.scrbl" () (gui-library) "wv-context")
|
||||
("scrbl/wv-element.scrbl" () (gui-library) "wv-element")
|
||||
("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"
|
||||
"net-doc"
|
||||
))
|
||||
|
||||
|
||||
3
js/boilerplate.css
Normal file
3
js/boilerplate.css
Normal file
@@ -0,0 +1,3 @@
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
@@ -2,7 +2,11 @@
|
||||
if (window.rkt_event_queue === undefined) { window.rkt_event_queue = []; }
|
||||
|
||||
window.rkt_put_evt = function(evt) {
|
||||
evt.timestamp = Date.now();
|
||||
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) {
|
||||
|
||||
113
js/menu.css
Normal file
113
js/menu.css
Normal 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
132
js/menu.js
Normal 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 = '>';
|
||||
}
|
||||
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');
|
||||
};
|
||||
6
main.rkt
6
main.rkt
@@ -6,12 +6,18 @@
|
||||
(require "private/wv-dialog.rkt")
|
||||
(require "private/wv-element.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"
|
||||
"private/wv-window.rkt"
|
||||
"private/wv-dialog.rkt"
|
||||
"private/wv-element.rkt"
|
||||
"private/wv-input.rkt"
|
||||
"private/rgba.rkt"
|
||||
"private/mimetypes.rkt"
|
||||
"private/menu.rkt"
|
||||
)
|
||||
webview-set-loglevel
|
||||
webview-version
|
||||
|
||||
Binary file not shown.
Binary file not shown.
145
private/menu.rkt
145
private/menu.rkt
@@ -1,60 +1,72 @@
|
||||
(module menu racket/base
|
||||
|
||||
(require json)
|
||||
(require json
|
||||
net/url)
|
||||
|
||||
(provide menu
|
||||
menu-item
|
||||
is-menu?
|
||||
menu-set-callback!
|
||||
menu-set-icon!
|
||||
menu-set-title!
|
||||
menu->json
|
||||
with-menu-item
|
||||
menu-for-each
|
||||
ww-menu-item-callback
|
||||
ww-menu-item-id
|
||||
ww-menu-id
|
||||
(provide wv-menu
|
||||
wv-menu-item
|
||||
is-wv-menu?
|
||||
wv-menu-set-callback!
|
||||
wv-menu-set-icon!
|
||||
wv-menu-set-title!
|
||||
wv-menu->json
|
||||
with-wv-menu-item
|
||||
wv-menu-for-each
|
||||
wv-menu-item-callback
|
||||
wv-menu-item-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)
|
||||
|
||||
(define-struct ww-menu
|
||||
(define-struct ww-menu*
|
||||
(id [items #:mutable])
|
||||
#:transparent
|
||||
)
|
||||
|
||||
(define (is-menu? mnu)
|
||||
(if (ww-menu? mnu)
|
||||
(if (list? (ww-menu-items mnu))
|
||||
|
||||
(define (wv-menu-item-callback mi)
|
||||
(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)
|
||||
(if (null? m)
|
||||
#t
|
||||
(if (ww-menu-item? (car m))
|
||||
(if (eq? (ww-menu-item-submenu (car m)) #f)
|
||||
(if (ww-menu-item*? (car m))
|
||||
(if (eq? (ww-menu-item*-submenu (car m)) #f)
|
||||
(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)
|
||||
))
|
||||
))
|
||||
(f (ww-menu-items mnu)))
|
||||
(f (ww-menu*-items mnu)))
|
||||
#f)
|
||||
#f))
|
||||
|
||||
(define (menu . items)
|
||||
(define (wv-menu . items)
|
||||
(let ((menu-id #f))
|
||||
(when (symbol? (car items))
|
||||
(set! menu-id (car items))
|
||||
(set! items (cdr items)))
|
||||
(when (list? (car items))
|
||||
(set! items (car items)))
|
||||
(make-ww-menu menu-id items)))
|
||||
(make-ww-menu* menu-id items)))
|
||||
|
||||
(define (menu-item id title
|
||||
#:icon-file [icon-file #f]
|
||||
(define (wv-menu-item id title
|
||||
#:icon-url [icon-url #f]
|
||||
#:callback [callback (lambda args #t)]
|
||||
#:submenu [submenu #f]
|
||||
#:separator [separator #f])
|
||||
@@ -62,47 +74,49 @@
|
||||
(error "menu-item needs an id of symbol?"))
|
||||
(unless (string? title)
|
||||
(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?"))
|
||||
(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?"))
|
||||
(unless (boolean? separator)
|
||||
(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))))
|
||||
(unless (is-menu? menu)
|
||||
(unless (is-wv-menu? 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)
|
||||
(let ((h (make-hasheq)))
|
||||
(hash-set! h 'id (format "~a" (ww-menu-item-id item)))
|
||||
(hash-set! h 'name (ww-menu-item-title item))
|
||||
(unless (eq? (ww-menu-item-icon-file item) #f)
|
||||
(hash-set! h 'icon (ww-menu-item-icon-file item)))
|
||||
(unless (eq? (ww-menu-item-submenu item) #f)
|
||||
(hash-set! h 'submenu (menu->hash (ww-menu-item-submenu item) fj)))
|
||||
(unless (eq? (ww-menu-item-separator item) #f)
|
||||
(hash-set! h 'id (format "~a" (ww-menu-item*-id item)))
|
||||
(hash-set! h 'name (ww-menu-item*-title item))
|
||||
(unless (eq? (ww-menu-item*-icon-url item) #f)
|
||||
(hash-set! h 'icon (ww-menu-item*-icon-url item)))
|
||||
(unless (eq? (ww-menu-item*-submenu item) #f)
|
||||
(hash-set! h 'submenu (wv-menu->hash (ww-menu-item*-submenu item) fj)))
|
||||
(unless (eq? (ww-menu-item*-separator item) #f)
|
||||
(hash-set! h 'separator #t))
|
||||
h
|
||||
)) items))
|
||||
)
|
||||
(let ((h (make-hasheq)))
|
||||
(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))))
|
||||
|
||||
(define (menu-for-each menu cb)
|
||||
(let ((items (ww-menu-items menu)))
|
||||
(define (wv-menu-for-each menu cb)
|
||||
(let ((items (ww-menu*-items menu)))
|
||||
(letrec ((f (λ (items)
|
||||
(if (null? items)
|
||||
#t
|
||||
(let ((item (car items)))
|
||||
(let ((submenu (ww-menu-item-submenu item)))
|
||||
(let ((submenu (ww-menu-item*-submenu item)))
|
||||
(if (eq? submenu #f)
|
||||
(cb item)
|
||||
(menu-for-each submenu cb)))
|
||||
(wv-menu-for-each submenu cb)))
|
||||
(f (cdr items))
|
||||
)
|
||||
)
|
||||
@@ -110,23 +124,23 @@
|
||||
))
|
||||
(f items))))
|
||||
|
||||
(define (menu->json menu)
|
||||
(define (wv-menu->json menu)
|
||||
(let ((o (open-output-string)))
|
||||
(write-json (menu->hash menu #t) o)
|
||||
(write-json (wv-menu->hash menu #t) o)
|
||||
(get-output-string o)))
|
||||
|
||||
(define (find-menu-item menu id)
|
||||
(let ((items (ww-menu-items menu)))
|
||||
(define (find-wv-menu-item menu id)
|
||||
(let ((items (ww-menu*-items menu)))
|
||||
(letrec ((f (λ (items)
|
||||
(if (null? items)
|
||||
#f
|
||||
(let ((item (car items)))
|
||||
(if (eq? (ww-menu-item-id item) id)
|
||||
(if (eq? (ww-menu-item*-id item) id)
|
||||
item
|
||||
(let ((submenu (ww-menu-item-submenu item)))
|
||||
(let ((submenu (ww-menu-item*-submenu item)))
|
||||
(if (eq? submenu #f)
|
||||
(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)
|
||||
(f (cdr items))
|
||||
found-item))
|
||||
@@ -136,37 +150,38 @@
|
||||
))
|
||||
(f items))))
|
||||
|
||||
(define (with-menu-item menu id cb)
|
||||
(unless (is-menu? menu)
|
||||
(define (with-wv-menu-item menu id cb)
|
||||
(unless (is-wv-menu? menu)
|
||||
(error "menu must be of is-menu?"))
|
||||
(unless (symbol? id)
|
||||
(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)
|
||||
(error (format "cannot find id'~a in given menu" id))
|
||||
(cb item)))
|
||||
menu)
|
||||
|
||||
(define (menu-set-title! menu id title)
|
||||
(define (wv-menu-set-title! menu id title)
|
||||
(unless (string? title)
|
||||
(error "title must be of string?"))
|
||||
(with-menu-item menu id
|
||||
(with-wv-menu-item menu id
|
||||
(λ (item)
|
||||
(set-ww-menu-item-title! item title))))
|
||||
(set-ww-menu-item*-title! item title))))
|
||||
|
||||
(define (menu-set-icon! menu id icon)
|
||||
(unless (or (eq? icon #f) (path? icon) (string? icon))
|
||||
(define (wv-menu-set-icon! menu id icon-url)
|
||||
(unless (or (eq? icon-url #f) (url? icon-url) (string? icon-url))
|
||||
(error "title must be of #f, string? or path?"))
|
||||
(with-menu-item menu id
|
||||
(with-wv-menu-item menu id
|
||||
(λ (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)
|
||||
(error "callback must be of procedure?"))
|
||||
(with-menu-item menu id
|
||||
(with-wv-menu-item menu id
|
||||
(λ (item)
|
||||
(set-ww-menu-item-callback! item cb))))
|
||||
(set-ww-menu-item*-callback! item cb))))
|
||||
|
||||
); end of module
|
||||
|
||||
|
||||
@@ -590,7 +590,7 @@
|
||||
; )))))
|
||||
(let ((handle (make-rkt-wv wv evt-queue evt-callback #t close-callback)))
|
||||
(thread (λ ()
|
||||
(sleep 1)
|
||||
(sleep 0.01)
|
||||
(letrec ((f (λ ()
|
||||
(let ((r (rkt-process-events handle)))
|
||||
(if (eq? r 'quit)
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
|
||||
(define webview-major 0)
|
||||
(define webview-minor 1)
|
||||
(define webview-patch 0)
|
||||
(define webview-patch 1)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"utils.rkt"
|
||||
"mimetypes.rkt"
|
||||
"rgba.rkt"
|
||||
"menu.rkt"
|
||||
finalizer
|
||||
racket/async-channel
|
||||
web-server/http
|
||||
@@ -73,6 +74,8 @@
|
||||
webview-set-html!
|
||||
webview-base-url
|
||||
|
||||
webview-set-menu!
|
||||
|
||||
webview-set-innerHTML!
|
||||
|
||||
webview-set-value!
|
||||
@@ -105,6 +108,7 @@
|
||||
|
||||
webview-standard-file-getter
|
||||
webview-default-boilerplate-js
|
||||
webview-default-boilerplate-css
|
||||
|
||||
webview-version
|
||||
webview-info
|
||||
@@ -123,16 +127,38 @@
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(define-runtime-path js-path "../js")
|
||||
(define-runtime-path css-path "../js")
|
||||
|
||||
(define (webview-default-boilerplate-js . custom-js)
|
||||
(let ((file (build-path js-path "boilerplate.js")))
|
||||
(let ((bjs (file->string file)))
|
||||
(let ((file (build-path js-path "boilerplate.js"))
|
||||
(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
|
||||
mjs
|
||||
(if (null? custom-js)
|
||||
""
|
||||
((car custom-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
|
||||
([context #:mutable]
|
||||
[port #:mutable]
|
||||
@@ -142,6 +168,7 @@
|
||||
[request-count #:mutable]
|
||||
[sec-token-cache #:mutable]
|
||||
[cert-ou-token #:mutable]
|
||||
[boilerplate-css #:mutable]
|
||||
)
|
||||
#:transparent
|
||||
)
|
||||
@@ -153,16 +180,16 @@
|
||||
)
|
||||
#:transparent)
|
||||
|
||||
(define re_head #px"[<][/][Hh][eE][aA][dD][>]")
|
||||
|
||||
(define (process-html context path out)
|
||||
(let ((html (file->string path)))
|
||||
(display html out)))
|
||||
; (boilerplate-js ((wv-context-boilerplate-js wv-win-handle))))
|
||||
; (set! html (string-replace html "<head>"
|
||||
; (string-append "<head>" "\n"
|
||||
; "<script>" "\n"
|
||||
; boilerplate-js "\n"
|
||||
; "</script>" "\n")))
|
||||
; (display html out)))
|
||||
(let ((html* (regexp-replace re_head html
|
||||
(string-append "<style>\n"
|
||||
(wv-context-boilerplate-css context)
|
||||
"\n</style>\n"
|
||||
"</head>"))))
|
||||
(display html* out))))
|
||||
|
||||
(define (process-file context ext path out)
|
||||
(let ((content (file->bytes path)))
|
||||
@@ -414,11 +441,13 @@
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(define/contract (webview-new-context file-getter
|
||||
#:boilerplate-js [bj (webview-default-boilerplate-js)])
|
||||
(->* (procedure?) (#:boilerplate-js string?) wv-context?)
|
||||
#:boilerplate-js [bj (webview-default-boilerplate-js)]
|
||||
#: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
|
||||
(make-lru 250 #:cmp eq?)
|
||||
(symbol->string (make-security-token))
|
||||
bc
|
||||
))
|
||||
(cert (generate-self-signed-cert 2048 365 '("127.0.0.1" "localhost")
|
||||
"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)
|
||||
(and (symbol? x)
|
||||
(or (eq? x 'error) (eq? x 'info) (eq? x 'debug) (eq? x 'warning))))
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
base-path
|
||||
[file-getter (webview-standard-file-getter base-path)]
|
||||
[context-js (λ () "")]
|
||||
[context-css (λ () "")]
|
||||
[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")]
|
||||
)
|
||||
|
||||
@@ -35,7 +37,8 @@
|
||||
(begin
|
||||
(set! wv-context
|
||||
(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]))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
(let ((x (inexact->exact (round (exact->inexact (+ px xx)))))
|
||||
(y (inexact->exact (round (exact->inexact (+ py yy)))))
|
||||
)
|
||||
(displayln "move")
|
||||
(displayln (format "move ~a ~a" x y))
|
||||
(send this move x y)
|
||||
(displayln "resize")
|
||||
(displayln (format "resize ~a ~a" x y))
|
||||
(send this resize dw dh)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
(define (event-handler wv evt)
|
||||
(let ((event (hash-ref evt 'event 'unknown-event))
|
||||
)
|
||||
(displayln evt)
|
||||
(cond
|
||||
((eq? event 'resize)
|
||||
(send this resized (hash-ref evt 'w) (hash-ref evt 'h)))
|
||||
@@ -163,7 +164,7 @@
|
||||
(let* ((je (hash-copy (hash-ref evt 'js-evt)))
|
||||
(e (make-hash)))
|
||||
(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 'event 'js-evt)
|
||||
(when (eq? (send this js-event e) 'wv-unhandled-js-event)
|
||||
@@ -245,7 +246,6 @@
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Commands
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -258,6 +258,15 @@
|
||||
(webview-remove-class! wv selector-or-id cl)
|
||||
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)
|
||||
(webview-devtools wv)
|
||||
this)
|
||||
|
||||
@@ -27,3 +27,4 @@ QHash<QString, QVariant> mkEvent()
|
||||
}
|
||||
|
||||
int EventContainer::evt_count = 0;
|
||||
qint64 EventContainer::ms_start = -1;
|
||||
|
||||
@@ -4,15 +4,24 @@
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <QDateTime>
|
||||
|
||||
class EventContainer : public QHash<QString, QVariant>
|
||||
{
|
||||
private:
|
||||
static int evt_count;
|
||||
static qint64 ms_start;
|
||||
public:
|
||||
EventContainer(const QString &evt) {
|
||||
if (ms_start < 0) {
|
||||
ms_start = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
this->insert("event", evt);
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ void Rktwebview_qt::processCommand(Command *cmd)
|
||||
"window.rkt_evt_frame_el = null;\n"
|
||||
"window.rkt_evt_frame_win = null;\n"
|
||||
"window.rkt_send_event = function(obj) {\n"
|
||||
" obj.timestamp = Date.now();\n"
|
||||
" //console.log('Sending event: ' + obj);\n"
|
||||
" window.rkt_event_queue.push(obj);\n"
|
||||
" if (window.rkt_evt_frame_el) {\n"
|
||||
|
||||
195
scrbl/menu.scrbl
Normal file
195
scrbl/menu.scrbl
Normal 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
60
scrbl/mimetypes.scrbl
Normal 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.
|
||||
64
scrbl/racket-webview-intro.scrbl
Normal file
64
scrbl/racket-webview-intro.scrbl
Normal 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.
|
||||
@@ -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
|
||||
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}
|
||||
|
||||
|
||||
@defproc[(webview-new-context
|
||||
[file-getter procedure?]
|
||||
[#:boilerplate-js boilerplate-js string?
|
||||
(webview-default-boilerplate-js)])
|
||||
(webview-default-boilerplate-js)]
|
||||
[#:boilerplate-css boilerplate-css string?
|
||||
(webview-default-boilerplate-css)])
|
||||
wv-context?]{
|
||||
|
||||
Creates a new context.
|
||||
|
||||
The function:
|
||||
|
||||
@itemlist[#:style 'compact
|
||||
@item{starts a local HTTPS server on a dynamically chosen port}
|
||||
@item{generates a self-signed certificate}
|
||||
@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
|
||||
collected.
|
||||
@@ -58,6 +71,7 @@ Returns the base URL of the context.
|
||||
This URL can be used to construct URLs from relative path information.
|
||||
}
|
||||
|
||||
|
||||
@defproc[(wv-context? [v any/c]) boolean?]{
|
||||
Recognizes context values.
|
||||
}
|
||||
@@ -147,6 +161,17 @@ Replaces the current document contents.
|
||||
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?]{
|
||||
Sets the window title.
|
||||
}
|
||||
@@ -525,9 +550,23 @@ Recognizes lists of filter entries.
|
||||
|
||||
@section{Utilities}
|
||||
|
||||
@defproc[(webview-default-boilerplate-js [f procedure?] ...)
|
||||
@defproc[(webview-default-boilerplate-js [custom-js procedure?] ...)
|
||||
string?]{
|
||||
|
||||
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
|
||||
|
||||
96
scrbl/rgba.scrbl
Normal file
96
scrbl/rgba.scrbl
Normal 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)"
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@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.
|
||||
|
||||
@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)]
|
||||
[context-js procedure?
|
||||
(λ () "")]
|
||||
[context-css procedure?
|
||||
(λ () "")]
|
||||
[boilerplate-js string?
|
||||
(webview-default-boilerplate-js context-js)]
|
||||
[boilerplate-css string?
|
||||
(webview-default-boilerplate-css context-css)]
|
||||
[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.
|
||||
|
||||
@@ -56,9 +67,14 @@ The constructor accepts the following initialization fields.
|
||||
@racket[(webview-standard-file-getter base-path)].}
|
||||
@item{@racket[context-js] is a procedure producing additional JavaScript for
|
||||
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
|
||||
underlying webview context. Its default value is
|
||||
@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
|
||||
@racket[wv-settings%] object. No default backend is provided; omitting it
|
||||
raises an error.}]
|
||||
|
||||
83
scrbl/wv-dialog.scrbl
Normal file
83
scrbl/wv-dialog.scrbl
Normal 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%].
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ If a parent is supplied, the underlying lower-level parent window is passed to
|
||||
@racket[webview-create].
|
||||
}
|
||||
|
||||
@defmethod[(context) any/c]{
|
||||
@defmethod[(context) (is-a?/c wv-context%)]{
|
||||
|
||||
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.
|
||||
|
||||
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]{
|
||||
|
||||
Reference in New Issue
Block a user