diff --git a/gui.rkt b/gui.rkt index 92bbee1..0551d5e 100644 --- a/gui.rkt +++ b/gui.rkt @@ -3,6 +3,7 @@ (require web-racket racket/runtime-path racket/gui + racket-sprintf "utils.rkt" "music-library.rkt" "translate.rkt" @@ -35,14 +36,70 @@ [html-file rktplayer-start] ) + (define closed #f) (define el-seeker #f) (define el-library #f) (define el-playlist #f) + (define el-at #f) + (define el-length #f) (define music-library (send settings get 'music-library (find-system-path 'home-dir))) (define current-music-path #f) (define playlist #f) - (define player (new player%)) + + (define current-at-seconds 0) + (define current-length-seconds 0) + + (define (update-time at-seconds length-seconds) + (let ((as (inexact->exact (round at-seconds))) + (ls (inexact->exact (round length-seconds)))) + (when (or (not (= current-at-seconds as)) + (not (= current-length-seconds ls))) + (set! current-at-seconds as) + (set! current-length-seconds ls) + (let ((as-str (sprintf "%02d:%02d:%02d" + (quotient as 3600) + (quotient (remainder as 3600) 60) + (remainder (remainder as 3600) 60))) + (ls-str (sprintf "%02d:%02d:%02d" + (quotient ls 3600) + (quotient (remainder ls 3600) 60) + (remainder (remainder ls 3600) 60))) + ) + (unless closed + (send el-at set-inner-html! as-str) + (send el-length set-inner-html! ls-str) + (let ((seeker (exact->inexact (/ (* 100 as) ls)))) + (displayln (format "seeker = ~a" seeker)) + (send el-seeker set! (format "~a" seeker))) + ) + ) + ) + ) + ) + + (define current-track-nr #f) + (define (update-track-nr nr) + (let ((id (λ () (string->symbol (format "track-~a" (+ current-track-nr 1)))))) + (unless (eq? current-track-nr #f) + (displayln (format "current track: ~a" (id))) + (let ((el (send this element (id)))) + (send el remove-class! "current"))) + + (set! current-track-nr nr) + + (unless (eq? current-track-nr #f) + (displayln (format "current track: ~a" (id))) + (let ((el (send this element (id)))) + (send el add-class! "current"))) + ) + ) + + (define player (new player% + [time-updater update-time] + [track-nr-updater update-track-nr] + [settings settings] + )) (define inner-html-handlers (make-hash)) @@ -61,6 +118,10 @@ (send el-seeker on-change! seek-reactor)) (set! el-library (send this element 'library)) + (set! el-playlist (send this element 'tracks)) + + (set! el-at (send this element 'time)) + (set! el-length (send this element 'totaltime)) (send this set-menu! (player-menu)) (send this connect-menu! 'm-quit (λ () (send this quit))) @@ -71,7 +132,10 @@ (define/public (update-playlist) - (displayln "Updating playlist") + (let ((html (send playlist to-html))) + (send el-playlist set-inner-html! html) + (update-track-nr current-track-nr) + ) ) (define/public (update-library) @@ -103,8 +167,7 @@ (λ (args) (send this path-choosen (cadr row)))) (send el connect 'click - (λ (args) - (displayln args))) + (λ (args) #t)) (send el connect 'contextmenu (λ (evt) (send this context-for-path evt (cadr row)))) @@ -137,26 +200,24 @@ ) (define/public (context-for-path evt path) - (let* ((mnu (menu 'library-popup - (menu-item 'm-play-this (tr "Play this") #:callback (λ () (send this play-path path))) - (menu-item 'm-booklet (tr "Open booklet") #:callback (λ () (send this open-booklet path))) - (menu-item 'm-folder (tr "Open containing folder") #:callback (λ () (send this open-folder path)) - #:submenu - (menu (menu-item 'm-idx (tr "Select Music Library Folder") #:separator #t) - (menu-item 'm-idy (tr "Quit") #:separator #t - #:submenu - (menu (menu-item 'mabd (tr "Ja")) - (menu-item 'mdedjk (tr "No")) - (menu-item 'sdakjfas (tr "akjfhalk")) - )) - )) - ) - ) - (js-evt (hash-ref evt 'js_evt (make-hash))) - (clientX (hash-ref js-evt 'clientX 60)) - (clientY (hash-ref js-evt 'clientY 60)) - ) - (send this popup-menu mnu clientX clientY) + (let ((items (list + (menu-item 'm-play-this (tr "Play this") #:callback (λ () (send this play-path path)))))) + (when (file-exists? path) + (set! items (append items + (list + (menu-item 'm-add-this (tr "Add this") #:callback (λ () (send this add-path path))))))) + (set! items (append items + (list + (menu-item 'm-booklet (tr "Open booklet") #:callback (λ () (send this open-booklet path))) ;; todo check if pdf file exists + (menu-item 'm-folder (tr "Open containing folder") #:callback (λ () (send this open-folder path))) + ))) + (let* ((mnu (menu 'library-popup items)) + (js-evt (hash-ref evt 'js_evt (make-hash))) + (clientX (hash-ref js-evt 'clientX 60)) + (clientY (hash-ref js-evt 'clientY 60)) + ) + (send this popup-menu mnu clientX clientY) + ) ) ) @@ -169,6 +230,11 @@ (send player play playlist) ) + (define/public (add-path path) + (send playlist add-track path) + (send this update-playlist) + ) + (define/public (open-booklet path) (displayln (format "Open booklet ~a" path))) @@ -201,6 +267,8 @@ (define/public (quit) (displayln (format "Quitting")) + (send player quit) + (set! closed #t) (send this close)) (define/public (select-library) diff --git a/gui/menu.js b/gui/menu.js new file mode 100644 index 0000000..a2abe05 --- /dev/null +++ b/gui/menu.js @@ -0,0 +1,128 @@ +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', item: menu.id }; + window._web_wire_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', item: id }; + window._web_wire_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) { + window._web_wire_popup_menu(menubar, -1, -1, 'menubar'); +}; diff --git a/gui/rktplayer.html b/gui/rktplayer.html index 3601211..b65c65d 100644 --- a/gui/rktplayer.html +++ b/gui/rktplayer.html @@ -4,7 +4,8 @@