Skip to content

Commit bbcb9d4

Browse files
committed
Window: visual effects that say what they did, and one meaning for a hidden title bar
SetVisualEffect returns whether the effect is in force, IsVisualEffectSupported answers beforehand, and GetVisualEffect reads the effect off the native window instead of off whichever wrapper was asked; Linux, Android, iOS and OpenHarmony say no rather than recording a value they cannot honour. MicaAlt, Hud, Popover and Menu are new. macOS: the effect view sits below a FlutterView's own layers, the Flutter view controller is left clear for as long as the effect lasts, and the window's background colour and opacity are no longer overwritten on the way. Windows: every effect now works on Windows 10 as a blur-behind kind, where all of them used to need Windows 11 22H2 and do nothing at all below it; on 22H2 they are system backdrops, which cover the title bar too. A window of the library's own class also keeps its own background colour instead of sharing one class brush with every other window. TitleBarStyle::Hidden means the bare window on all six platforms now - no title bar and no window control buttons - where macOS used to keep the traffic lights on a transparent bar and the style reset their visibility as a side effect. The transparent bar macOS gave for free became a property of its own: SetContentUnderTitleBar() takes the title bar into the content area and leaves it an overlay with the buttons still on it, which is what a window whose background runs to the top edge wants. IsContentUnderTitleBarSupported() answers for the platform, and only macOS says yes: on Windows the caption buttons DWM keeps drawing answer HTCLIENT once the client area takes the caption band, so they would have to be drawn and hit-tested by hand, and on Linux a GTK header bar is a sibling above the content rather than an overlay over it. window_visual_effect_example walks a window through every effect and checks what the API reports about it.
1 parent 4117030 commit bbcb9d4

13 files changed

Lines changed: 939 additions & 202 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ add_subdirectory(examples/window_drag_session_example)
3535
add_subdirectory(examples/window_lifecycle_example)
3636
add_subdirectory(examples/window_parent_example)
3737
add_subdirectory(examples/window_shadow_example)
38+
add_subdirectory(examples/window_visual_effect_example)
3839
add_subdirectory(examples/drag_drop_example)
3940
add_subdirectory(examples/desktop_features_example)
4041

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
3+
project(window_visual_effect_example VERSION 0.0.1 LANGUAGES CXX)
4+
5+
# Set C++ standard
6+
set(CMAKE_CXX_STANDARD 17)
7+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
8+
9+
# Add example program
10+
add_executable(window_visual_effect_example
11+
"main.cpp"
12+
)
13+
14+
# Link main library
15+
target_link_libraries(window_visual_effect_example PRIVATE nativeapi)
16+
17+
# Set example program properties
18+
set_target_properties(window_visual_effect_example PROPERTIES
19+
OUTPUT_NAME "window_visual_effect_example"
20+
)
21+
22+
# Set example program compile options (macOS only)
23+
if(APPLE)
24+
set_source_files_properties("main.cpp"
25+
PROPERTIES
26+
COMPILE_FLAGS "-x objective-c++"
27+
)
28+
endif()
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#include <chrono>
2+
#include <cstdlib>
3+
#include <functional>
4+
#include <iostream>
5+
#include <string>
6+
#include <thread>
7+
#include <utility>
8+
#include <vector>
9+
10+
#include "nativeapi.h"
11+
12+
using nativeapi::Color;
13+
using nativeapi::VisualEffect;
14+
using nativeapi::Window;
15+
16+
// Walks a window through every VisualEffect, over a plain red window that gives the
17+
// effects something to show. What the API reports is checked here - the exit code is the
18+
// number of expectations that did not hold - and each step prints a "STEP" line once it is
19+
// on screen, so that whoever watches (tools/gui has a test that does) knows when to look.
20+
//
21+
// It drives the windows itself and needs no input.
22+
int main() {
23+
nativeapi::SetMainThread();
24+
25+
int failures = 0;
26+
auto expect = [&](const std::string& what, bool ok) {
27+
std::cout << (ok ? "PASS " : "FAIL ") << what << std::endl;
28+
if (!ok) {
29+
failures++;
30+
}
31+
};
32+
33+
const std::vector<std::pair<VisualEffect, std::string>> effects = {
34+
{VisualEffect::Blur, "Blur"}, {VisualEffect::Acrylic, "Acrylic"},
35+
{VisualEffect::Hud, "Hud"}, {VisualEffect::Popover, "Popover"},
36+
{VisualEffect::Menu, "Menu"}, {VisualEffect::Mica, "Mica"},
37+
{VisualEffect::MicaAlt, "MicaAlt"},
38+
};
39+
40+
std::shared_ptr<Window> backdrop;
41+
std::shared_ptr<Window> window;
42+
VisualEffect in_force = VisualEffect::None;
43+
44+
std::vector<std::function<void()>> steps = {[&] {
45+
backdrop = std::make_shared<Window>();
46+
backdrop->SetTitle("Backdrop");
47+
backdrop->SetBackgroundColor(Color::FromRGBA(255, 0, 0));
48+
backdrop->SetContentSize({560, 420});
49+
backdrop->SetPosition({200, 160});
50+
backdrop->Show();
51+
52+
window = std::make_shared<Window>();
53+
window->SetTitle("Visual effect");
54+
window->SetContentSize({400, 260});
55+
window->SetPosition({280, 240});
56+
// A child stays above its parent, whichever of the two is brought forward.
57+
window->SetParentWindow(backdrop);
58+
window->Show();
59+
window->Focus();
60+
expect("a window starts without a visual effect",
61+
window->GetVisualEffect() == VisualEffect::None);
62+
expect("no effect is supported everywhere",
63+
Window::IsVisualEffectSupported(VisualEffect::None));
64+
std::cout << "STEP None applied" << std::endl;
65+
}};
66+
67+
for (const auto& [effect, name] : effects) {
68+
steps.push_back([&, effect = effect, name = name] {
69+
const bool supported = Window::IsVisualEffectSupported(effect);
70+
const bool applied = window->SetVisualEffect(effect);
71+
expect(name + ": SetVisualEffect agrees with IsVisualEffectSupported", applied == supported);
72+
if (applied) {
73+
in_force = effect;
74+
}
75+
expect(name + ": GetVisualEffect is the effect in force",
76+
window->GetVisualEffect() == in_force);
77+
std::cout << "STEP " << name << (applied ? " applied" : " refused") << std::endl;
78+
});
79+
}
80+
81+
steps.push_back([&] {
82+
// A background color given while an effect is active waits for the effect to go.
83+
window->SetBackgroundColor(Color::FromRGBA(0, 0, 255));
84+
expect("the background color is kept while an effect is active",
85+
window->GetBackgroundColor().b == 255 && window->GetBackgroundColor().r == 0);
86+
std::cout << "STEP Background " << (in_force == VisualEffect::None ? "shown" : "waiting")
87+
<< std::endl;
88+
});
89+
steps.push_back([&] {
90+
expect("the effect can be removed", window->SetVisualEffect(VisualEffect::None));
91+
expect("no effect is in force afterwards", window->GetVisualEffect() == VisualEffect::None);
92+
std::cout << "STEP Removed applied" << std::endl;
93+
});
94+
steps.push_back([&] {
95+
std::cout << (failures == 0 ? "ALL PASS" : "FAILED") << std::endl;
96+
// Not Quit(): how a platform's loop ends must not decide the exit code.
97+
std::cout.flush();
98+
std::_Exit(failures);
99+
});
100+
101+
std::thread driver([&] {
102+
for (size_t i = 0; i < steps.size(); i++) {
103+
// The windows stay as they first come up for a while: a watcher has to find them.
104+
std::this_thread::sleep_for(std::chrono::milliseconds(i == 1 ? 6000 : 2500));
105+
nativeapi::RunOnMainThread(steps[i]);
106+
}
107+
});
108+
driver.detach();
109+
110+
// On Windows destroying any window of the library ends the loop (its window
111+
// procedure posts WM_QUIT), so enter it again: the last step ends the process.
112+
while (true) {
113+
nativeapi::Application::GetInstance().Run();
114+
}
115+
}

‎src/capi/window_c.cpp‎

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,41 @@ native_title_bar_style_t native_window_get_title_bar_style(native_window_t windo
894894
}
895895
}
896896

897+
bool native_window_set_content_under_title_bar(native_window_t window, bool is_content_under_title_bar) {
898+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Window>(window);
899+
if (!self) {
900+
return false;
901+
}
902+
try {
903+
return self->SetContentUnderTitleBar(is_content_under_title_bar);
904+
} catch (...) {
905+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_set_content_under_title_bar");
906+
return false;
907+
}
908+
}
909+
910+
bool native_window_is_content_under_title_bar(native_window_t window) {
911+
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Window>(window);
912+
if (!self) {
913+
return false;
914+
}
915+
try {
916+
return self->IsContentUnderTitleBar();
917+
} catch (...) {
918+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_is_content_under_title_bar");
919+
return false;
920+
}
921+
}
922+
923+
bool native_window_is_content_under_title_bar_supported(void) {
924+
try {
925+
return nativeapi::Window::IsContentUnderTitleBarSupported();
926+
} catch (...) {
927+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_is_content_under_title_bar_supported");
928+
return false;
929+
}
930+
}
931+
897932
void native_window_set_has_shadow(native_window_t window, bool has_shadow) {
898933
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Window>(window);
899934
if (!self) {
@@ -948,17 +983,16 @@ float native_window_get_opacity(native_window_t window) {
948983
}
949984
}
950985

951-
void native_window_set_visual_effect(native_window_t window, native_visual_effect_t effect) {
986+
bool native_window_set_visual_effect(native_window_t window, native_visual_effect_t effect) {
952987
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Window>(window);
953988
if (!self) {
954-
return;
989+
return false;
955990
}
956991
try {
957-
self->SetVisualEffect(to_cpp_visual_effect(effect));
958-
return;
992+
return self->SetVisualEffect(to_cpp_visual_effect(effect));
959993
} catch (...) {
960994
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_set_visual_effect");
961-
return;
995+
return false;
962996
}
963997
}
964998

@@ -975,6 +1009,15 @@ native_visual_effect_t native_window_get_visual_effect(native_window_t window) {
9751009
}
9761010
}
9771011

1012+
bool native_window_is_visual_effect_supported(native_visual_effect_t effect) {
1013+
try {
1014+
return nativeapi::Window::IsVisualEffectSupported(to_cpp_visual_effect(effect));
1015+
} catch (...) {
1016+
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_is_visual_effect_supported");
1017+
return false;
1018+
}
1019+
}
1020+
9781021
void native_window_set_background_color(native_window_t window, native_color_t color) {
9791022
auto self = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::Window>(window);
9801023
if (!self) {

‎src/capi/window_c.h‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ typedef enum {
3232
NATIVE_VISUAL_EFFECT_BLUR = 1,
3333
NATIVE_VISUAL_EFFECT_ACRYLIC = 2,
3434
NATIVE_VISUAL_EFFECT_MICA = 3,
35+
NATIVE_VISUAL_EFFECT_MICA_ALT = 4,
36+
NATIVE_VISUAL_EFFECT_HUD = 5,
37+
NATIVE_VISUAL_EFFECT_POPOVER = 6,
38+
NATIVE_VISUAL_EFFECT_MENU = 7,
3539
} native_visual_effect_t;
3640

3741
typedef enum {
@@ -287,6 +291,15 @@ void native_window_set_title_bar_style(native_window_t window, native_title_bar_
287291
FFI_PLUGIN_EXPORT
288292
native_title_bar_style_t native_window_get_title_bar_style(native_window_t window);
289293

294+
FFI_PLUGIN_EXPORT
295+
bool native_window_set_content_under_title_bar(native_window_t window, bool is_content_under_title_bar);
296+
297+
FFI_PLUGIN_EXPORT
298+
bool native_window_is_content_under_title_bar(native_window_t window);
299+
300+
FFI_PLUGIN_EXPORT
301+
bool native_window_is_content_under_title_bar_supported(void);
302+
290303
FFI_PLUGIN_EXPORT
291304
void native_window_set_has_shadow(native_window_t window, bool has_shadow);
292305

@@ -300,11 +313,14 @@ FFI_PLUGIN_EXPORT
300313
float native_window_get_opacity(native_window_t window);
301314

302315
FFI_PLUGIN_EXPORT
303-
void native_window_set_visual_effect(native_window_t window, native_visual_effect_t effect);
316+
bool native_window_set_visual_effect(native_window_t window, native_visual_effect_t effect);
304317

305318
FFI_PLUGIN_EXPORT
306319
native_visual_effect_t native_window_get_visual_effect(native_window_t window);
307320

321+
FFI_PLUGIN_EXPORT
322+
bool native_window_is_visual_effect_supported(native_visual_effect_t effect);
323+
308324
FFI_PLUGIN_EXPORT
309325
void native_window_set_background_color(native_window_t window, native_color_t color);
310326

@@ -420,6 +436,14 @@ inline native_visual_effect_t to_c_visual_effect(nativeapi::VisualEffect value)
420436
return NATIVE_VISUAL_EFFECT_ACRYLIC;
421437
case nativeapi::VisualEffect::Mica:
422438
return NATIVE_VISUAL_EFFECT_MICA;
439+
case nativeapi::VisualEffect::MicaAlt:
440+
return NATIVE_VISUAL_EFFECT_MICA_ALT;
441+
case nativeapi::VisualEffect::Hud:
442+
return NATIVE_VISUAL_EFFECT_HUD;
443+
case nativeapi::VisualEffect::Popover:
444+
return NATIVE_VISUAL_EFFECT_POPOVER;
445+
case nativeapi::VisualEffect::Menu:
446+
return NATIVE_VISUAL_EFFECT_MENU;
423447
default:
424448
return NATIVE_VISUAL_EFFECT_NONE;
425449
}
@@ -435,6 +459,14 @@ inline nativeapi::VisualEffect to_cpp_visual_effect(native_visual_effect_t value
435459
return nativeapi::VisualEffect::Acrylic;
436460
case NATIVE_VISUAL_EFFECT_MICA:
437461
return nativeapi::VisualEffect::Mica;
462+
case NATIVE_VISUAL_EFFECT_MICA_ALT:
463+
return nativeapi::VisualEffect::MicaAlt;
464+
case NATIVE_VISUAL_EFFECT_HUD:
465+
return nativeapi::VisualEffect::Hud;
466+
case NATIVE_VISUAL_EFFECT_POPOVER:
467+
return nativeapi::VisualEffect::Popover;
468+
case NATIVE_VISUAL_EFFECT_MENU:
469+
return nativeapi::VisualEffect::Menu;
438470
default:
439471
return nativeapi::VisualEffect::None;
440472
}

‎src/platform/android/window_android.cpp‎

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@ namespace nativeapi {
1414
// Private implementation class
1515
class Window::Impl {
1616
public:
17-
Impl(ANativeWindow* window) : native_window_(window), visual_effect_(VisualEffect::None) {}
17+
Impl(ANativeWindow* window) : native_window_(window) {}
1818
ANativeWindow* native_window_;
19-
VisualEffect visual_effect_;
2019
};
2120

2221
Window::Window() : pimpl_(std::make_unique<Impl>(nullptr)) {}
@@ -343,6 +342,18 @@ TitleBarStyle Window::GetTitleBarStyle() const {
343342
return TitleBarStyle::Normal;
344343
}
345344

345+
bool Window::SetContentUnderTitleBar(bool is_content_under_title_bar) {
346+
return false;
347+
}
348+
349+
bool Window::IsContentUnderTitleBar() const {
350+
return false;
351+
}
352+
353+
bool Window::IsContentUnderTitleBarSupported() {
354+
return false;
355+
}
356+
346357
void Window::SetHasShadow(bool has_shadow) {
347358
ALOGW("SetHasShadow not supported on Android");
348359
}
@@ -359,13 +370,16 @@ float Window::GetOpacity() const {
359370
return 1.0f;
360371
}
361372

362-
void Window::SetVisualEffect(VisualEffect effect) {
363-
pimpl_->visual_effect_ = effect;
364-
ALOGW("SetVisualEffect not supported on Android");
373+
bool Window::SetVisualEffect(VisualEffect effect) {
374+
return effect == VisualEffect::None;
365375
}
366376

367377
VisualEffect Window::GetVisualEffect() const {
368-
return pimpl_->visual_effect_;
378+
return VisualEffect::None;
379+
}
380+
381+
bool Window::IsVisualEffectSupported(VisualEffect effect) {
382+
return effect == VisualEffect::None;
369383
}
370384

371385
void Window::SetBackgroundColor(const Color& color) {

0 commit comments

Comments
 (0)