pikvm/pikvm#1316: web: keep stream window maximized

This commit is contained in:
Maxim Devaev 2025-05-17 20:42:09 +03:00
parent 53980c0e68
commit 818ff6321e
14 changed files with 293 additions and 126 deletions

View File

@ -982,14 +982,16 @@
<div class="window" id="stream-ocr-window"> <div class="window" id="stream-ocr-window">
<div class="hidden" id="stream-ocr-selection"></div> <div class="hidden" id="stream-ocr-selection"></div>
</div> </div>
<div class="window window-resizable" id="stream-window"> <div class="window window-resizable" id="stream-window" data-show-maximized>
<div class="window-header" id="stream-window-header"> <div class="window-header" id="stream-window-header">
<div class="window-grab">MJPEG</div> <div class="window-grab">MJPEG</div>
<button class="window-button-close"><b>&times;</b></button> <div class="window-buttons">
<button class="window-button-maximize">&#9744;</button> <button class="window-button-full-screen">&#10530;</button>
<button class="window-button-original">&bull;</button> <button class="window-button-enter-full-tab">&#9650;</button>
<button class="window-button-enter-full-tab">&#9650;</button> <button class="window-button-original">&bull;</button>
<button class="window-button-full-screen">&#10530;</button> <button class="window-button-maximize">&#9744;</button>
<button class="window-button-close"><b>&times;</b></button>
</div>
</div> </div>
<div id="stream-info"></div> <div id="stream-info"></div>
<button class="window-button-exit-full-tab">&#9660;</button> <button class="window-button-exit-full-tab">&#9660;</button>
@ -1032,10 +1034,13 @@
</div> </div>
</div> </div>
</div> </div>
<div class="window" id="keyboard-window"> <div class="window" id="keyboard-window" data-show-centered>
<div class="window-header" id="keyboard-window-header"> <div class="window-header" id="keyboard-window-header">
<div class="window-grab">Virtual Keyboard</div> <div class="window-grab">Virtual Keyboard</div>
<button class="window-button-close"><b>&times;</b></button> <div class="window-buttons">
<button class="window-button-original">&bull;</button>
<button class="window-button-close"><b>&times;</b></button>
</div>
</div> </div>
<div class="keypad" id="keyboard-desktop" align="center"> <div class="keypad" id="keyboard-desktop" align="center">
<div class="keypad-block"> <div class="keypad-block">
@ -2111,10 +2116,13 @@
</div> </div>
</div> </div>
</div> </div>
<div class="window" id="switch-window" style="width: min-content"> <div class="window" id="switch-window" data-show-centered style="width: min-content">
<div class="window-header"> <div class="window-header">
<div class="window-grab">Switch settings</div> <div class="window-grab">Switch settings</div>
<button class="window-button-close"><b>&times;</b></button> <div class="window-buttons">
<button class="window-button-original">&bull;</button>
<button class="window-button-close"><b>&times;</b></button>
</div>
</div> </div>
<div class="tabs-box"> <div class="tabs-box">
<input checked type="radio" name="switch-tab-button" id="switch-tab-edid-button"> <input checked type="radio" name="switch-tab-button" id="switch-tab-edid-button">
@ -2258,10 +2266,13 @@
</div> </div>
</div> </div>
</div> </div>
<div class="window" id="about-window"> <div class="window" id="about-window" data-show-centered>
<div class="window-header"> <div class="window-header">
<div class="window-grab">About</div> <div class="window-grab">About</div>
<button class="window-button-close"><b>&times;</b></button> <div class="window-buttons">
<button class="window-button-original">&bull;</button>
<button class="window-button-close"><b>&times;</b></button>
</div>
</div> </div>
<div id="about"> <div id="about">
<table> <table>
@ -2961,15 +2972,18 @@
<p class="text credits"><a target="_blank" href="https://pikvm.org">PiKVM Project</a>&nbsp; | &nbsp;<a target="_blank" href="https://docs.pikvm.org">Documentation</a>&nbsp; | &nbsp;<a target="_blank" href="https://pikvm.org/support">Support</a></p> <p class="text credits"><a target="_blank" href="https://pikvm.org">PiKVM Project</a>&nbsp; | &nbsp;<a target="_blank" href="https://docs.pikvm.org">Documentation</a>&nbsp; | &nbsp;<a target="_blank" href="https://pikvm.org/support">Support</a></p>
</div> </div>
</div> </div>
<div class="window window-resizable" id="webterm-window" style="width: 720px; height: 480px"> <div class="window window-resizable" id="webterm-window" data-show-centered style="display: flex; min-width: 720px; min-height: 480px">
<div class="window-header"> <div class="window-header">
<div class="window-grab">Terminal</div> <div class="window-grab">Terminal</div>
<button class="window-button-close"><b>&times;</b></button> <div class="window-buttons">
<button class="window-button-maximize">&#9744;</button> <button class="window-button-original">&bull;</button>
<!-- Терминал глючит из-за зажимаемой клавиши ESC для выхода--> <button class="window-button-maximize">&#9744;</button>
<!-- button(class="window-button-full-screen") &#10530;--> <button class="window-button-close"><b>&times;</b></button>
<!-- Терминал глючит из-за зажимаемой клавиши ESC для выхода-->
<!-- button(class="window-button-full-screen") &#10530;-->
</div>
</div> </div>
<iframe id="webterm-iframe" src="" style="width: 100%; height: 100%"></iframe> <iframe id="webterm-iframe" src="" style="width: 100%; flex: 1"></iframe>
</div> </div>
<ul class="navbar-bg-tips"> <ul class="navbar-bg-tips">
<li class="left"> <li class="left">

View File

@ -10,10 +10,12 @@ mixin about_tab(name, title, checked=false)
span.code-comment No data span.code-comment No data
.window#about-window .window#about-window(data-show-centered)
.window-header .window-header
.window-grab About .window-grab About
button.window-button-close #[b &times;] .window-buttons
button.window-button-original &bull;
button.window-button-close #[b &times;]
#about #about
table table

View File

@ -30,10 +30,12 @@ mixin lamp(cls)
img(class=`inline-lamp-small ${cls} led-gray` src=`${svg_dir}/led-square.svg`) img(class=`inline-lamp-small ${cls} led-gray` src=`${svg_dir}/led-square.svg`)
.window#keyboard-window .window#keyboard-window(data-show-centered)
.window-header#keyboard-window-header .window-header#keyboard-window-header
.window-grab Virtual Keyboard .window-grab Virtual Keyboard
button.window-button-close #[b &times;] .window-buttons
button.window-button-original &bull;
button.window-button-close #[b &times;]
.keypad#keyboard-desktop(align="center") .keypad#keyboard-desktop(align="center")
.keypad-block .keypad-block

View File

@ -1,14 +1,15 @@
.window#stream-ocr-window .window#stream-ocr-window
.hidden#stream-ocr-selection .hidden#stream-ocr-selection
.window.window-resizable#stream-window .window.window-resizable#stream-window(data-show-maximized)
.window-header#stream-window-header .window-header#stream-window-header
.window-grab MJPEG .window-grab MJPEG
button.window-button-close #[b &times;] .window-buttons
button.window-button-maximize &#9744; button.window-button-full-screen &#10530;
button.window-button-original &bull; button.window-button-enter-full-tab &#9650;
button.window-button-enter-full-tab &#9650; button.window-button-original &bull;
button.window-button-full-screen &#10530; button.window-button-maximize &#9744;
button.window-button-close #[b &times;]
#stream-info #stream-info

View File

@ -15,10 +15,12 @@ mixin color_slider_tr(name, title)
td #[button(id=`switch-color-${name}-default-button` class="small" title="Reset default") &#8635;] td #[button(id=`switch-color-${name}-default-button` class="small" title="Reset default") &#8635;]
.window#switch-window(style="width: min-content") .window#switch-window(data-show-centered style="width: min-content")
.window-header .window-header
.window-grab Switch settings .window-grab Switch settings
button.window-button-close #[b &times;] .window-buttons
button.window-button-original &bull;
button.window-button-close #[b &times;]
.tabs-box .tabs-box
+switch_tab("edid", "EDIDs collection", true) +switch_tab("edid", "EDIDs collection", true)

View File

@ -1,9 +1,11 @@
.window.window-resizable#webterm-window(style="width: 720px; height: 480px") .window.window-resizable#webterm-window(data-show-centered style="display: flex; min-width: 720px; min-height: 480px")
.window-header .window-header
.window-grab Terminal .window-grab Terminal
button.window-button-close #[b &times;] .window-buttons
button.window-button-maximize &#9744; button.window-button-original &bull;
// Терминал глючит из-за зажимаемой клавиши ESC для выхода button.window-button-maximize &#9744;
// button(class="window-button-full-screen") &#10530; button.window-button-close #[b &times;]
// Терминал глючит из-за зажимаемой клавиши ESC для выхода
// button(class="window-button-full-screen") &#10530;
iframe#webterm-iframe(src="" style="width: 100%; height: 100%") iframe#webterm-iframe(src="" style="width: 100%; flex: 1")

View File

@ -34,6 +34,11 @@ div#stream-window {
width: 100% !important; width: 100% !important;
-webkit-transform: translateX(-50%) !important; -webkit-transform: translateX(-50%) !important;
transform: translateX(-50%) !important; transform: translateX(-50%) !important;
/* Ignore stream's resize_hook() */
aspect-ratio: unset !important;
max-width: unset !important;
max-height: unset !important;
} }
div#stream-window::after { div#stream-window::after {

View File

@ -45,6 +45,7 @@
--cs-window-header-grabbed-bg: #436a8a; --cs-window-header-grabbed-bg: #436a8a;
--cs-window-header-grabbed-fg: white; --cs-window-header-grabbed-fg: white;
--cs-window-closer-default-fg: #6c7481; --cs-window-closer-default-fg: #6c7481;
--cs-window-button-special-fg: #009b48;
--cs-code-default-bg: #17191d; --cs-code-default-bg: #17191d;
--cs-code-default-fg: #aaaaaa; --cs-code-default-fg: #aaaaaa;

View File

@ -40,7 +40,7 @@ div.window-resizable {
div.window-active { div.window-active {
border: var(--border-window-active-2px) !important; border: var(--border-window-active-2px) !important;
} }
div.window-resizable.window-active::after { div.window-resizable.window-active:not(.window-full-tab)::after {
content: ""; content: "";
width: 0; width: 0;
height: 0; height: 0;
@ -61,6 +61,11 @@ div.window:fullscreen {
width: 100% !important; width: 100% !important;
height: 100% !important; height: 100% !important;
padding: 0px !important; padding: 0px !important;
/* Ignore stream's resize_hook() */
aspect-ratio: unset !important;
max-width: unset !important;
max-height: unset !important;
} }
div.window:fullscreen::after { div.window:fullscreen::after {
display: none; display: none;
@ -75,6 +80,11 @@ div.window:-webkit-full-screen {
width: 100% !important; width: 100% !important;
height: 100% !important; height: 100% !important;
padding: 0px !important; padding: 0px !important;
/* Ignore stream's resize_hook() */
aspect-ratio: unset !important;
max-width: unset !important;
max-height: unset !important;
} }
div.window:-webkit-full-screen::after { div.window:-webkit-full-screen::after {
display: none; display: none;
@ -88,6 +98,11 @@ div.window.window-full-tab {
width: 100% !important; width: 100% !important;
height: 100% !important; height: 100% !important;
padding: 0px !important; padding: 0px !important;
/* Ignore stream's resize_hook() */
aspect-ratio: unset !important;
max-width: unset !important;
max-height: unset !important;
} }
div.window div.window-header { div.window div.window-header {
@ -126,36 +141,35 @@ div.window div.window-header-grabbed {
border-bottom: var(--border-intensive-thin); border-bottom: var(--border-intensive-thin);
} }
div.window div.window-header button.window-button-full-screen, div.window div.window-header div.window-buttons {
div.window div.window-header button.window-button-enter-full-tab,
div.window div.window-header button.window-button-original,
div.window div.window-header button.window-button-maximize,
div.window div.window-header button.window-button-close {
line-height: 1px;
border: none;
position: absolute; position: absolute;
top: -2px; top: -2px;
right: 0;
font-size: 0px;
background-color: var(--cs-window-default-bg);
}
div.window div.window-header div.window-buttons button.window-button-full-screen,
div.window div.window-header div.window-buttons button.window-button-enter-full-tab,
div.window div.window-header div.window-buttons button.window-button-original,
div.window div.window-header div.window-buttons button.window-button-maximize,
div.window div.window-header div.window-buttons button.window-button-close {
line-height: 1px;
border: none;
border-radius: 0px;
width: 44px; width: 44px;
height: 24px; height: 24px;
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
margin-left: 1px;
color: var(--cs-window-closer-default-fg); color: var(--cs-window-closer-default-fg);
display: inline-block; display: inline-block;
} }
div.window div.window-header button.window-button-full-screen { div.window[data-centered] div.window-header div.window-buttons button.window-button-original {
right: 180px; color: var(--cs-window-button-special-fg);
} }
div.window div.window-header button.window-button-enter-full-tab { div.window[data-maximized] div.window-header div.window-buttons button.window-button-maximize {
right: 135px; color: var(--cs-window-button-special-fg);
}
div.window div.window-header button.window-button-original {
right: 90px;
}
div.window div.window-header button.window-button-maximize {
right: 45px;
}
div.window div.window-header button.window-button-close {
right: 0px;
} }
div.window button.window-button-exit-full-tab { div.window button.window-button-exit-full-tab {

View File

@ -43,7 +43,7 @@ export function Streamer() {
var __res = {"width": 640, "height": 480}; var __res = {"width": 640, "height": 480};
var __init__ = function() { var __init__ = function() {
__streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo); __streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo, __organizeHook);
$("stream-led").title = "Stream inactive"; $("stream-led").title = "Stream inactive";
@ -110,6 +110,7 @@ export function Streamer() {
$("stream-window").show_hook = () => __applyState(__state); $("stream-window").show_hook = () => __applyState(__state);
$("stream-window").close_hook = () => __applyState(null); $("stream-window").close_hook = () => __applyState(null);
$("stream-window").organize_hook = __organizeHook;
}; };
/************************************************************************/ /************************************************************************/
@ -294,6 +295,11 @@ export function Streamer() {
el_grab.innerText = el_info.innerText = title; el_grab.innerText = el_info.innerText = title;
}; };
var __organizeHook = function() {
let geo = self.getGeometry();
wm.setAspectRatio($("stream-window"), geo.width, geo.height);
};
var __resetStream = function(mode=null) { var __resetStream = function(mode=null) {
if (mode === null) { if (mode === null) {
mode = __streamer.getMode(); mode = __streamer.getMode();
@ -303,16 +309,16 @@ export function Streamer() {
if (mode === "janus") { if (mode === "janus") {
let allow_audio = !$("stream-video").muted; let allow_audio = !$("stream-video").muted;
let allow_mic = $("stream-mic-switch").checked; let allow_mic = $("stream-mic-switch").checked;
__streamer = new JanusStreamer(__setActive, __setInactive, __setInfo, orient, allow_audio, allow_mic); __streamer = new JanusStreamer(__setActive, __setInactive, __setInfo, __organizeHook, orient, allow_audio, allow_mic);
// Firefox doesn't support RTP orientation: // Firefox doesn't support RTP orientation:
// - https://bugzilla.mozilla.org/show_bug.cgi?id=1316448 // - https://bugzilla.mozilla.org/show_bug.cgi?id=1316448
tools.feature.setEnabled($("stream-orient"), !tools.browser.is_firefox); tools.feature.setEnabled($("stream-orient"), !tools.browser.is_firefox);
} else { } else {
if (mode === "media") { if (mode === "media") {
__streamer = new MediaStreamer(__setActive, __setInactive, __setInfo, orient); __streamer = new MediaStreamer(__setActive, __setInactive, __setInfo, __organizeHook, orient);
tools.feature.setEnabled($("stream-orient"), true); tools.feature.setEnabled($("stream-orient"), true);
} else { // mjpeg } else { // mjpeg
__streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo); __streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo, __organizeHook);
tools.feature.setEnabled($("stream-orient"), false); tools.feature.setEnabled($("stream-orient"), false);
} }
tools.feature.setEnabled($("stream-audio"), false); // Enabling in stream_janus.js tools.feature.setEnabled($("stream-audio"), false); // Enabling in stream_janus.js

View File

@ -29,7 +29,7 @@ import {tools, $} from "../tools.js";
var _Janus = null; var _Janus = null;
export function JanusStreamer(__setActive, __setInactive, __setInfo, __orient, __allow_audio, __allow_mic) { export function JanusStreamer(__setActive, __setInactive, __setInfo, __organizeHook, __orient, __allow_audio, __allow_mic) {
var self = this; var self = this;
/************************************************************************/ /************************************************************************/
@ -48,6 +48,8 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __orient, _
var __state = null; var __state = null;
var __frames = 0; var __frames = 0;
var __res = {"width": -1, "height": -1};
var __resize_listener_installed = false;
var __ice = null; var __ice = null;
@ -84,11 +86,28 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __orient, _
__state = state; __state = state;
__stop = false; __stop = false;
__ensureJanus(false); __ensureJanus(false);
if (!__resize_listener_installed) {
$("stream-video").addEventListener("resize", __videoResizeHandler);
__resize_listener_installed = true;
}
}; };
self.stopStream = function() { self.stopStream = function() {
__stop = true; __stop = true;
__destroyJanus(); __destroyJanus();
if (__resize_listener_installed) {
$("stream-video").removeEventListener("resize", __videoResizeHandler);
__resize_listener_installed = false;
}
};
var __videoResizeHandler = function(ev) {
let el = ev.target;
if (__res.width !== el.videoWidth || __res.height !== el.videoHeight) {
__res.width = el.videoWidth;
__res.height = el.videoHeight;
__organizeHook();
}
}; };
var __ensureJanus = function(internal) { var __ensureJanus = function(internal) {

View File

@ -26,7 +26,7 @@
import {tools, $} from "../tools.js"; import {tools, $} from "../tools.js";
export function MediaStreamer(__setActive, __setInactive, __setInfo, __orient) { export function MediaStreamer(__setActive, __setInactive, __setInfo, __organizeHook, __orient) {
var self = this; var self = this;
/************************************************************************/ /************************************************************************/
@ -282,6 +282,7 @@ export function MediaStreamer(__setActive, __setInactive, __setInfo, __orient) {
if (__canvas.width !== width || __canvas.height !== height) { if (__canvas.width !== width || __canvas.height !== height) {
__canvas.width = width; __canvas.width = width;
__canvas.height = height; __canvas.height = height;
__organizeHook();
} }
if (__orient === 0) { if (__orient === 0) {

View File

@ -27,7 +27,7 @@ import {ROOT_PREFIX} from "../vars.js";
import {tools, $} from "../tools.js"; import {tools, $} from "../tools.js";
export function MjpegStreamer(__setActive, __setInactive, __setInfo) { export function MjpegStreamer(__setActive, __setInactive, __setInfo, __organizeHook) {
var self = this; var self = this;
/************************************************************************/ /************************************************************************/
@ -62,6 +62,7 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
if (__id.length > 0 && __id in __state.stream.clients_stat) { if (__id.length > 0 && __id in __state.stream.clients_stat) {
__setStreamActive(); __setStreamActive();
__stopChecking(); __stopChecking();
__organizeHook();
} else { } else {
__ensureChecking(); __ensureChecking();
} }

View File

@ -60,21 +60,59 @@ function __WindowManager() {
__windows.push(el_win); __windows.push(el_win);
if (el_win.classList.contains("window-resizable") && window.ResizeObserver) { if (el_win.classList.contains("window-resizable") && window.ResizeObserver) {
el_win.__observer_timer = null;
new ResizeObserver(function() { new ResizeObserver(function() {
// При переполнении рабочей области сократить размер окна по высоте. // Таймер нужен чтобы остановить дребезг ресайза: observer вызывает
// По ширине оно настраивается само в CSS. // __organizeWindow(), который сам по себе триггерит observer.
let view = self.getViewGeometry(); if (el_win.__observer_timer === null || el_win.__manual_resizing) {
let rect = el_win.getBoundingClientRect(); __organizeWindow(el_win, !el_win.__manual_resizing);
if ((rect.bottom - rect.top) > (view.bottom - view.top)) { if (el_win.__observer_timer !== null) {
let ratio = (rect.bottom - rect.top) / (view.bottom - view.top); clearTimeout(el_win.__observer_timer);
el_win.style.height = view.bottom - view.top + "px"; }
el_win.style.width = Math.round((rect.right - rect.left) / ratio) + "px"; el_win.__observer_timer = setTimeout(function() {
} el_win.__observer_timer = null;
}, 100);
if (el_win.hasAttribute("data-centered")) {
__centerWindow(el_win);
} }
}).observe(el_win); }).observe(el_win);
el_win.addEventListener("pointerrawupdate", function(ev) {
// События pointerdown и touchdown не генерируются при ресайзе за уголок,
// поэтому отлавливаем pointerrawupdate для тач-событий.
let events = ev.getCoalescedEvents();
for (ev of events) {
if (
ev.target === el_win && ev.pointerType === "touch" && ev.buttons
&& Math.abs(el_win.clientWidth - ev.offsetX) < 20
&& Math.abs(el_win.clientHeight - ev.offsetY) < 20
) {
__setWindowMca(el_win, false, null, true);
break;
}
}
});
el_win.addEventListener("mousedown", function(ev) {
if (
ev.target === el_win
&& Math.abs(el_win.clientWidth - ev.offsetX) < 20
&& Math.abs(el_win.clientHeight - ev.offsetY) < 20
) {
el_win.__manual_resizing = true;
}
});
document.addEventListener("mouseup", function() {
if (el_win.__manual_resizing) {
__organizeWindow(el_win);
}
el_win.__manual_resizing = false;
});
document.addEventListener("mousemove", function(ev) {
if (el_win.__manual_resizing) {
__setWindowMca(el_win, false, null, true);
if (!ev.buttons) {
__organizeWindow(el_win);
el_win.__manual_resizing = false;
}
}
});
} }
{ {
@ -90,8 +128,9 @@ function __WindowManager() {
if (el) { if (el) {
el.title = "Maximize window"; el.title = "Maximize window";
tools.el.setOnClick(el, function() { tools.el.setOnClick(el, function() {
__maximizeWindow(el_win); __setWindowMca(el_win, true, false, false);
__activateLastWindow(el_win); __organizeWindow(el_win);
__activateWindow(el_win);
}); });
} }
} }
@ -101,10 +140,11 @@ function __WindowManager() {
if (el) { if (el) {
el.title = "Reduce window to its original size and center it"; el.title = "Reduce window to its original size and center it";
tools.el.setOnClick(el, function() { tools.el.setOnClick(el, function() {
__setWindowMca(el_win, false, true, false);
el_win.style.width = ""; el_win.style.width = "";
el_win.style.height = ""; el_win.style.height = "";
__centerWindow(el_win); __organizeWindow(el_win);
__activateLastWindow(el_win); __activateWindow(el_win);
}); });
} }
} }
@ -125,7 +165,7 @@ function __WindowManager() {
el.title = "Go to full-screen mode"; el.title = "Go to full-screen mode";
tools.el.setOnClick(el, function() { tools.el.setOnClick(el, function() {
__setFullScreenWindow(el_win); __setFullScreenWindow(el_win);
__activateLastWindow(el_win); __activateWindow(el_win);
}); });
} }
} }
@ -301,20 +341,46 @@ function __WindowManager() {
return promise; return promise;
}; };
var __setWindowMca = function(el_win, maximized, centered, adjusted) {
if (maximized !== null) {
el_win.toggleAttribute("data-maximized", maximized);
if (maximized) {
el_win.removeAttribute("data-centered");
}
}
if (centered !== null) {
el_win.toggleAttribute("data-centered", centered);
if (centered) {
el_win.removeAttribute("data-maximized");
}
}
if (adjusted !== null) {
el_win.toggleAttribute("data-adjusted", adjusted);
if (adjusted) {
el_win.removeAttribute("data-maximized");
}
}
};
self.showWindow = function(el_win) { self.showWindow = function(el_win) {
let center = false;
let showed = false; let showed = false;
if (!self.isWindowVisible(el_win)) { if (!self.isWindowVisible(el_win)) {
center = true;
showed = true; showed = true;
} }
__organizeWindow(el_win, center);
if (!el_win.hasAttribute("data-adjusted")) {
if (el_win.hasAttribute("data-show-maximized") && !el_win.hasAttribute("data-centered")) {
__setWindowMca(el_win, true, false, false);
} else if (el_win.hasAttribute("data-show-centered") && !el_win.hasAttribute("data-maximized")) {
__setWindowMca(el_win, false, true, false);
}
}
__organizeWindow(el_win);
el_win.style.visibility = "visible"; el_win.style.visibility = "visible";
__activateWindow(el_win); __activateWindow(el_win);
if (el_win.show_hook) { if (showed && el_win.show_hook) {
if (showed) { el_win.show_hook();
el_win.show_hook();
}
} }
}; };
@ -346,6 +412,18 @@ function __WindowManager() {
setTimeout(() => __activateWindow(el_win), 100); setTimeout(() => __activateWindow(el_win), 100);
}; };
self.setAspectRatio = function(el_win, width, height) {
// XXX: Values from CSS
width += 9 + 9 + 2 + 2;
height += 30 + 9 + 2 + 2;
el_win.__aspect_ratio_width = width;
el_win.__aspect_ratio_height = height;
el_win.style.maxWidth = "fit-content";
el_win.style.maxHeight = "fit-content";
el_win.style.aspectRatio = `${width} / ${height}`;
__organizeWindow(el_win, true, false);
};
var __closeWindow = function(el_win) { var __closeWindow = function(el_win) {
el_win.focus(); el_win.focus();
el_win.blur(); el_win.blur();
@ -438,23 +516,20 @@ function __WindowManager() {
var __organizeWindowsOnBrowserResize = function() { var __organizeWindowsOnBrowserResize = function() {
for (let el_win of $$("window")) { for (let el_win of $$("window")) {
if (el_win.style.visibility === "visible") { if (el_win.style.visibility === "visible") {
if (tools.browser.is_mobile && el_win.classList.contains("window-resizable")) {
// FIXME: При смене ориентации на мобильном браузере надо сбрасывать
// настройки окна стрима, поэтому тут стоит вот этот костыль
el_win.style.width = "";
el_win.style.height = "";
}
__organizeWindow(el_win); __organizeWindow(el_win);
} }
} }
}; };
var __organizeWindow = function(el_win, center=false) { var __organizeWindow = function(el_win, auto_shrink=true, organize_hook=true) {
let view = self.getViewGeometry(); if (organize_hook && el_win.organize_hook) {
let rect = el_win.getBoundingClientRect(); el_win.organize_hook();
}
if (el_win.classList.contains("window-resizable")) { if (auto_shrink && el_win.classList.contains("window-resizable")) {
// При переполнении рабочей области сократить размер окна // При переполнении рабочей области сократить размер окна
let view = self.getViewGeometry();
let rect = el_win.getBoundingClientRect();
if ((rect.bottom - rect.top) > (view.bottom - view.top)) { if ((rect.bottom - rect.top) > (view.bottom - view.top)) {
let ratio = (rect.bottom - rect.top) / (view.bottom - view.top); let ratio = (rect.bottom - rect.top) / (view.bottom - view.top);
el_win.style.height = view.bottom - view.top + "px"; el_win.style.height = view.bottom - view.top + "px";
@ -463,32 +538,65 @@ function __WindowManager() {
if ((rect.right - rect.left) > (view.right - view.left)) { if ((rect.right - rect.left) > (view.right - view.left)) {
el_win.style.width = view.right - view.left + "px"; el_win.style.width = view.right - view.left + "px";
} }
rect = el_win.getBoundingClientRect();
} }
if (el_win.hasAttribute("data-centered") || center) { if (el_win.hasAttribute("data-maximized")) {
__centerWindow(el_win); __organizeMaximizeWindow(el_win);
} else if (el_win.hasAttribute("data-centered")) {
__organizeCenterWindow(el_win);
} else { } else {
if (rect.top <= view.top) { __organizeFitWindow(el_win);
el_win.style.top = view.top + "px";
} else if (rect.bottom > view.bottom) {
el_win.style.top = view.bottom - rect.height + "px";
}
if (rect.left <= view.left) {
el_win.style.left = view.left + "px";
} else if (rect.right > view.right) {
el_win.style.left = view.right - rect.width + "px";
}
} }
}; };
var __centerWindow = function(el_win) { var __organizeCenterWindow = function(el_win) {
let view = self.getViewGeometry(); let view = self.getViewGeometry();
let rect = el_win.getBoundingClientRect(); let rect = el_win.getBoundingClientRect();
el_win.style.top = Math.max(view.top, Math.round((view.bottom - rect.height) / 2)) + "px"; el_win.style.top = Math.max(view.top, Math.round((view.bottom - rect.height) / 2)) + "px";
el_win.style.left = Math.round((view.right - rect.width) / 2) + "px"; el_win.style.left = Math.round((view.right - rect.width) / 2) + "px";
el_win.setAttribute("data-centered", ""); };
var __organizeMaximizeWindow = function(el_win) {
let view = self.getViewGeometry();
el_win.style.top = view.top + "px";
let aw = el_win.__aspect_ratio_width;
let ah = el_win.__aspect_ratio_height;
let gw = view.right - view.left;
let gh = view.bottom - view.top;
if (aw && ah) {
if (aw / gw < ah / gh) {
el_win.style.width = "";
el_win.style.height = gh + "px";
} else {
el_win.style.left = "";
el_win.style.height = "";
el_win.style.width = gw + "px";
}
}/* else {
el_win.style.width = gw + "px";
el_win.style.height = gh + "px";
}*/
let rect = el_win.getBoundingClientRect();
el_win.style.left = Math.round((view.right - rect.width) / 2) + "px";
};
var __organizeFitWindow = function(el_win) {
let view = self.getViewGeometry();
let rect = el_win.getBoundingClientRect();
if (rect.top <= view.top) {
el_win.style.top = view.top + "px";
} else if (rect.bottom > view.bottom) {
el_win.style.top = view.bottom - rect.height + "px";
}
if (rect.left <= view.left) {
el_win.style.left = view.left + "px";
} else if (rect.right > view.right) {
el_win.style.left = view.right - rect.width + "px";
}
}; };
var __activateLastWindow = function(el_except_win=null) { var __activateLastWindow = function(el_except_win=null) {
@ -584,7 +692,7 @@ function __WindowManager() {
return; return;
} }
el_win.removeAttribute("data-centered"); __setWindowMca(el_win, false, false, true);
ev = (ev || window.ev); ev = (ev || window.ev);
ev.preventDefault(); ev.preventDefault();
@ -612,8 +720,6 @@ function __WindowManager() {
} }
} }
el_win.setAttribute("data-centered", "");
document.addEventListener("mousemove", doMoving); document.addEventListener("mousemove", doMoving);
document.addEventListener("mouseup", stopMoving); document.addEventListener("mouseup", stopMoving);
@ -659,14 +765,5 @@ function __WindowManager() {
el_win.focus(el_win); // Почему-то теряется фокус el_win.focus(el_win); // Почему-то теряется фокус
}; };
var __maximizeWindow = function(el_win) {
let el_navbar = $("navbar");
let vertical_offset = (el_navbar ? el_navbar.offsetHeight : 0);
el_win.style.left = "0px";
el_win.style.top = vertical_offset + "px";
el_win.style.width = window.innerWidth + "px";
el_win.style.height = window.innerHeight - vertical_offset + "px";
};
__init__(); __init__();
} }