App-thema’s, meerschermen, annotaties en grafiekslides #1
6 changed files with 225 additions and 6 deletions
|
|
@ -42,8 +42,8 @@ class FullscreenPresenter extends StatefulWidget {
|
|||
});
|
||||
|
||||
/// Entry point used by the app: pick dual-screen mode when a second display is
|
||||
/// available (macOS), otherwise the single-window presenter. Any failure to
|
||||
/// open the second window falls back to single-window mode.
|
||||
/// available on desktop, otherwise the single-window presenter. Any failure
|
||||
/// to open the second window falls back to single-window mode.
|
||||
static Future<void> present(
|
||||
BuildContext context, {
|
||||
required List<Slide> slides,
|
||||
|
|
@ -52,15 +52,21 @@ class FullscreenPresenter extends StatefulWidget {
|
|||
required int initialIndex,
|
||||
TlpLevel tlp = TlpLevel.none,
|
||||
}) async {
|
||||
var dual = false;
|
||||
if (Platform.isMacOS) {
|
||||
var displayCount = 0;
|
||||
if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) {
|
||||
try {
|
||||
final displays = await screenRetriever.getAllDisplays();
|
||||
dual = displays.length >= 2;
|
||||
displayCount = displays.length;
|
||||
} catch (_) {
|
||||
dual = false;
|
||||
displayCount = 0;
|
||||
}
|
||||
}
|
||||
final dual = shouldUseDualScreen(
|
||||
isMacOS: Platform.isMacOS,
|
||||
isWindows: Platform.isWindows,
|
||||
isLinux: Platform.isLinux,
|
||||
displayCount: displayCount,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
if (dual) {
|
||||
await showDualScreen(
|
||||
|
|
@ -204,6 +210,16 @@ class FullscreenPresenter extends StatefulWidget {
|
|||
State<FullscreenPresenter> createState() => _FullscreenPresenterState();
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
bool shouldUseDualScreen({
|
||||
required bool isMacOS,
|
||||
required bool isWindows,
|
||||
required bool isLinux,
|
||||
required int displayCount,
|
||||
}) {
|
||||
return (isMacOS || isWindows || isLinux) && displayCount >= 2;
|
||||
}
|
||||
|
||||
Future<bool> _wakeLockEnabled() async {
|
||||
try {
|
||||
return await WakelockPlus.enabled;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
|
||||
#include "desktop_multi_window/desktop_multi_window_plugin.h"
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
struct _MyApplication {
|
||||
|
|
@ -89,6 +90,8 @@ static void my_application_activate(GApplication* application) {
|
|||
gtk_widget_realize(GTK_WIDGET(view));
|
||||
|
||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||
desktop_multi_window_plugin_set_window_created_callback(
|
||||
[](FlPluginRegistry* registry) { fl_register_plugins(registry); });
|
||||
|
||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,57 @@ void main() {
|
|||
Slide.create(SlideType.bullets).copyWith(title: 'Tweede', bullets: ['b']),
|
||||
];
|
||||
|
||||
test('dual-screen mode is available on every desktop platform', () {
|
||||
expect(
|
||||
shouldUseDualScreen(
|
||||
isMacOS: true,
|
||||
isWindows: false,
|
||||
isLinux: false,
|
||||
displayCount: 2,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
shouldUseDualScreen(
|
||||
isMacOS: false,
|
||||
isWindows: true,
|
||||
isLinux: false,
|
||||
displayCount: 2,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
shouldUseDualScreen(
|
||||
isMacOS: false,
|
||||
isWindows: false,
|
||||
isLinux: true,
|
||||
displayCount: 2,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('dual-screen mode requires a desktop platform and two displays', () {
|
||||
expect(
|
||||
shouldUseDualScreen(
|
||||
isMacOS: true,
|
||||
isWindows: false,
|
||||
isLinux: false,
|
||||
displayCount: 1,
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
expect(
|
||||
shouldUseDualScreen(
|
||||
isMacOS: false,
|
||||
isWindows: false,
|
||||
isLinux: false,
|
||||
displayCount: 2,
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('starts in audience view without presenter chrome', (
|
||||
tester,
|
||||
) async {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,32 @@
|
|||
#include "flutter_window.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
|
||||
bool ReadExternalArgument(FlValue* arguments) {
|
||||
if (arguments == nullptr ||
|
||||
fl_value_get_type(arguments) != FL_VALUE_TYPE_MAP) {
|
||||
return true;
|
||||
}
|
||||
FlValue* external = fl_value_lookup_string(arguments, "external");
|
||||
if (external == nullptr ||
|
||||
fl_value_get_type(external) != FL_VALUE_TYPE_BOOL) {
|
||||
return true;
|
||||
}
|
||||
return fl_value_get_bool(external);
|
||||
}
|
||||
|
||||
gboolean CloseWindowOnIdle(gpointer data) {
|
||||
GtkWidget* window = GTK_WIDGET(data);
|
||||
gtk_widget_destroy(window);
|
||||
g_object_unref(window);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FlutterWindow::FlutterWindow(const std::string& id,
|
||||
const std::string& argument,
|
||||
GtkWidget* window)
|
||||
|
|
@ -42,6 +67,49 @@ void FlutterWindow::HandleWindowMethod(const gchar* method,
|
|||
} else if (strcmp(method, "window_hide") == 0) {
|
||||
Hide();
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
|
||||
} else if (strcmp(method, "window_close") == 0) {
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
|
||||
if (window_) {
|
||||
g_idle_add(CloseWindowOnIdle, g_object_ref(window_));
|
||||
}
|
||||
} else if (strcmp(method, "window_coverScreen") == 0) {
|
||||
if (!window_) {
|
||||
response = FL_METHOD_RESPONSE(
|
||||
fl_method_error_response_new("-1", "window is not available",
|
||||
nullptr));
|
||||
} else {
|
||||
GtkWindow* window = GTK_WINDOW(window_);
|
||||
GdkScreen* screen = gtk_window_get_screen(window);
|
||||
GdkWindow* gdk_window = gtk_widget_get_window(window_);
|
||||
const gint monitor_count = gdk_screen_get_n_monitors(screen);
|
||||
gint current_monitor = gdk_window
|
||||
? gdk_screen_get_monitor_at_window(screen,
|
||||
gdk_window)
|
||||
: gdk_screen_get_primary_monitor(screen);
|
||||
if (current_monitor < 0) {
|
||||
current_monitor = 0;
|
||||
}
|
||||
|
||||
gint target_monitor = current_monitor;
|
||||
if (ReadExternalArgument(arguments) && monitor_count > 1) {
|
||||
for (gint i = 0; i < monitor_count; ++i) {
|
||||
if (i != current_monitor) {
|
||||
target_monitor = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GdkRectangle bounds;
|
||||
gdk_screen_get_monitor_geometry(screen, target_monitor, &bounds);
|
||||
gtk_window_unfullscreen(window);
|
||||
gtk_window_set_decorated(window, FALSE);
|
||||
gtk_window_move(window, bounds.x, bounds.y);
|
||||
gtk_window_resize(window, bounds.width, bounds.height);
|
||||
gtk_window_fullscreen_on_monitor(window, screen, target_monitor);
|
||||
gtk_widget_show(window_);
|
||||
response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
|
||||
}
|
||||
} else {
|
||||
g_autofree gchar* error_msg = g_strdup_printf("unknown method: %s", method);
|
||||
response = FL_METHOD_RESPONSE(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,42 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
struct MonitorSearch {
|
||||
HMONITOR current = nullptr;
|
||||
HMONITOR external = nullptr;
|
||||
HMONITOR fallback = nullptr;
|
||||
};
|
||||
|
||||
inline BOOL CALLBACK FindPresentationMonitor(HMONITOR monitor,
|
||||
HDC,
|
||||
LPRECT,
|
||||
LPARAM data) {
|
||||
auto* search = reinterpret_cast<MonitorSearch*>(data);
|
||||
if (!search->fallback) {
|
||||
search->fallback = monitor;
|
||||
}
|
||||
if (monitor != search->current && !search->external) {
|
||||
search->external = monitor;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
inline bool ReadExternalArgument(const flutter::EncodableMap* arguments) {
|
||||
if (!arguments) {
|
||||
return true;
|
||||
}
|
||||
const auto it = arguments->find(flutter::EncodableValue("external"));
|
||||
if (it == arguments->end()) {
|
||||
return true;
|
||||
}
|
||||
const auto* external = std::get_if<bool>(&it->second);
|
||||
return external ? *external : true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class FlutterWindowWrapper {
|
||||
public:
|
||||
FlutterWindowWrapper(const std::string& window_id,
|
||||
|
|
@ -51,6 +87,45 @@ class FlutterWindowWrapper {
|
|||
::ShowWindow(hwnd_, SW_HIDE);
|
||||
}
|
||||
result->Success();
|
||||
} else if (method == "window_close") {
|
||||
result->Success();
|
||||
if (hwnd_) {
|
||||
::PostMessage(hwnd_, WM_CLOSE, 0, 0);
|
||||
}
|
||||
} else if (method == "window_coverScreen") {
|
||||
if (!hwnd_) {
|
||||
result->Error("-1", "window is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
MonitorSearch search;
|
||||
search.current = ::MonitorFromWindow(hwnd_, MONITOR_DEFAULTTONEAREST);
|
||||
::EnumDisplayMonitors(
|
||||
nullptr, nullptr, FindPresentationMonitor,
|
||||
reinterpret_cast<LPARAM>(&search));
|
||||
|
||||
HMONITOR target = search.current;
|
||||
if (ReadExternalArgument(arguments) && search.external) {
|
||||
target = search.external;
|
||||
} else if (!target) {
|
||||
target = search.fallback;
|
||||
}
|
||||
|
||||
MONITORINFO monitor_info{sizeof(MONITORINFO)};
|
||||
if (!target || !::GetMonitorInfo(target, &monitor_info)) {
|
||||
result->Error("-1", "unable to find a display");
|
||||
return;
|
||||
}
|
||||
|
||||
const RECT bounds = monitor_info.rcMonitor;
|
||||
::SetWindowLongPtr(hwnd_, GWL_STYLE, WS_POPUP | WS_VISIBLE);
|
||||
::SetWindowLongPtr(hwnd_, GWL_EXSTYLE,
|
||||
::GetWindowLongPtr(hwnd_, GWL_EXSTYLE) &
|
||||
~WS_EX_WINDOWEDGE);
|
||||
::SetWindowPos(hwnd_, HWND_TOP, bounds.left, bounds.top,
|
||||
bounds.right - bounds.left, bounds.bottom - bounds.top,
|
||||
SWP_FRAMECHANGED | SWP_SHOWWINDOW);
|
||||
result->Success();
|
||||
} else {
|
||||
result->Error("-1", "unknown method: " + method);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <optional>
|
||||
|
||||
#include "desktop_multi_window/desktop_multi_window_plugin.h"
|
||||
#include "flutter/generated_plugin_registrant.h"
|
||||
|
||||
FlutterWindow::FlutterWindow(const flutter::DartProject& project)
|
||||
|
|
@ -25,6 +26,11 @@ bool FlutterWindow::OnCreate() {
|
|||
return false;
|
||||
}
|
||||
RegisterPlugins(flutter_controller_->engine());
|
||||
DesktopMultiWindowSetWindowCreatedCallback([](void* controller) {
|
||||
auto* flutter_view_controller =
|
||||
reinterpret_cast<flutter::FlutterViewController*>(controller);
|
||||
RegisterPlugins(flutter_view_controller->engine());
|
||||
});
|
||||
SetChildContent(flutter_controller_->view()->GetNativeWindow());
|
||||
|
||||
flutter_controller_->engine()->SetNextFrameCallback([&]() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue