| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "flutter/shell/platform/windows/flutter_windows_view.h" |
| |
| #include <chrono> |
| |
| #include "flutter/common/constants.h" |
| #include "flutter/fml/make_copyable.h" |
| #include "flutter/fml/platform/win/wstring_conversion.h" |
| #include "flutter/fml/synchronization/waitable_event.h" |
| #include "flutter/shell/platform/common/accessibility_bridge.h" |
| #include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" |
| #include "flutter/shell/platform/windows/text_input_plugin.h" |
| #include "flutter/third_party/accessibility/ax/platform/ax_platform_node_win.h" |
| |
| namespace flutter { |
| |
| namespace { |
| // The maximum duration to block the platform thread for while waiting |
| // for a window resize operation to complete. |
| constexpr std::chrono::milliseconds kWindowResizeTimeout{100}; |
| |
| /// Returns true if the surface will be updated as part of the resize process. |
| /// |
| /// This is called on window resize to determine if the platform thread needs |
| /// to be blocked until the frame with the right size has been rendered. It |
| /// should be kept in-sync with how the engine deals with a new surface request |
| /// as seen in `CreateOrUpdateSurface` in `GPUSurfaceGL`. |
| bool SurfaceWillUpdate(size_t cur_width, |
| size_t cur_height, |
| size_t target_width, |
| size_t target_height) { |
| // TODO (https://github.com/flutter/flutter/issues/65061) : Avoid special |
| // handling for zero dimensions. |
| bool non_zero_target_dims = target_height > 0 && target_width > 0; |
| bool not_same_size = |
| (cur_height != target_height) || (cur_width != target_width); |
| return non_zero_target_dims && not_same_size; |
| } |
| |
| /// Update the surface's swap interval to block until the v-blank iff |
| /// the system compositor is disabled. |
| void UpdateVsync(const FlutterWindowsEngine& engine, |
| egl::WindowSurface* surface, |
| bool needs_vsync) { |
| egl::Manager* egl_manager = engine.egl_manager(); |
| if (!egl_manager) { |
| return; |
| } |
| |
| auto update_vsync = [egl_manager, surface, needs_vsync]() { |
| if (!surface || !surface->IsValid()) { |
| return; |
| } |
| |
| if (!surface->MakeCurrent()) { |
| FML_LOG(ERROR) << "Unable to make the render surface current to update " |
| "the swap interval"; |
| return; |
| } |
| |
| if (!surface->SetVSyncEnabled(needs_vsync)) { |
| FML_LOG(ERROR) << "Unable to update the render surface's swap interval"; |
| } |
| |
| if (!egl_manager->render_context()->ClearCurrent()) { |
| FML_LOG(ERROR) << "Unable to clear current surface after updating " |
| "the swap interval"; |
| } |
| }; |
| |
| // Updating the vsync makes the EGL context and render surface current. |
| // If the engine is running, the render surface should only be made current on |
| // the raster thread. If the engine is initializing, the raster thread doesn't |
| // exist yet and the render surface can be made current on the platform |
| // thread. |
| if (engine.running()) { |
| engine.PostRasterThreadTask(update_vsync); |
| } else { |
| update_vsync(); |
| } |
| } |
| |
| /// Destroys a rendering surface that backs a Flutter view. |
| void DestroyWindowSurface(const FlutterWindowsEngine& engine, |
| std::unique_ptr<egl::WindowSurface> surface) { |
| // EGL surfaces are used on the raster thread if the engine is running. |
| // There may be pending raster tasks that use this surface. Destroy the |
| // surface on the raster thread to avoid concurrent uses. |
| if (engine.running()) { |
| engine.PostRasterThreadTask(fml::MakeCopyable( |
| [surface = std::move(surface)] { surface->Destroy(); })); |
| } else { |
| // There's no raster thread if engine isn't running. The surface can be |
| // destroyed on the platform thread. |
| surface->Destroy(); |
| } |
| } |
| |
| } // namespace |
| |
| FlutterWindowsView::FlutterWindowsView( |
| FlutterViewId view_id, |
| FlutterWindowsEngine* engine, |
| std::unique_ptr<WindowBindingHandler> window_binding, |
| std::shared_ptr<WindowsProcTable> windows_proc_table) |
| : view_id_(view_id), |
| engine_(engine), |
| windows_proc_table_(std::move(windows_proc_table)) { |
| if (windows_proc_table_ == nullptr) { |
| windows_proc_table_ = std::make_shared<WindowsProcTable>(); |
| } |
| |
| // Take the binding handler, and give it a pointer back to self. |
| binding_handler_ = std::move(window_binding); |
| binding_handler_->SetView(this); |
| } |
| |
| FlutterWindowsView::~FlutterWindowsView() { |
| // The view owns the child window. |
| // Notify the engine the view's child window will no longer be visible. |
| engine_->OnWindowStateEvent(GetWindowHandle(), WindowStateEvent::kHide); |
| |
| if (surface_) { |
| DestroyWindowSurface(*engine_, std::move(surface_)); |
| } |
| } |
| |
| bool FlutterWindowsView::OnEmptyFrameGenerated() { |
| // Called on the raster thread. |
| std::unique_lock<std::mutex> lock(resize_mutex_); |
| |
| if (surface_ == nullptr || !surface_->IsValid()) { |
| return false; |
| } |
| |
| if (resize_status_ != ResizeState::kResizeStarted) { |
| return true; |
| } |
| |
| if (!ResizeRenderSurface(resize_target_height_, resize_target_width_)) { |
| return false; |
| } |
| |
| // Platform thread is blocked for the entire duration until the |
| // resize_status_ is set to kDone by |OnFramePresented|. |
| resize_status_ = ResizeState::kFrameGenerated; |
| return true; |
| } |
| |
| bool FlutterWindowsView::OnFrameGenerated(size_t width, size_t height) { |
| // Called on the raster thread. |
| std::unique_lock<std::mutex> lock(resize_mutex_); |
| |
| if (surface_ == nullptr || !surface_->IsValid()) { |
| return false; |
| } |
| |
| if (resize_status_ != ResizeState::kResizeStarted) { |
| return true; |
| } |
| |
| if (resize_target_width_ != width || resize_target_height_ != height) { |
| return false; |
| } |
| |
| if (!ResizeRenderSurface(resize_target_width_, resize_target_height_)) { |
| return false; |
| } |
| |
| // Platform thread is blocked for the entire duration until the |
| // resize_status_ is set to kDone by |OnFramePresented|. |
| resize_status_ = ResizeState::kFrameGenerated; |
| return true; |
| } |
| |
| void FlutterWindowsView::UpdateFlutterCursor(const std::string& cursor_name) { |
| binding_handler_->UpdateFlutterCursor(cursor_name); |
| } |
| |
| void FlutterWindowsView::SetFlutterCursor(HCURSOR cursor) { |
| binding_handler_->SetFlutterCursor(cursor); |
| } |
| |
| void FlutterWindowsView::ForceRedraw() { |
| if (resize_status_ == ResizeState::kDone) { |
| // Request new frame. |
| engine_->ScheduleFrame(); |
| } |
| } |
| |
| bool FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) { |
| // Called on the platform thread. |
| std::unique_lock<std::mutex> lock(resize_mutex_); |
| |
| if (!engine_->egl_manager()) { |
| SendWindowMetrics(width, height, binding_handler_->GetDpiScale()); |
| return true; |
| } |
| |
| if (!surface_ || !surface_->IsValid()) { |
| SendWindowMetrics(width, height, binding_handler_->GetDpiScale()); |
| return true; |
| } |
| |
| // We're using OpenGL rendering. Resizing the surface must happen on the |
| // raster thread. |
| bool surface_will_update = |
| SurfaceWillUpdate(surface_->width(), surface_->height(), width, height); |
| if (!surface_will_update) { |
| SendWindowMetrics(width, height, binding_handler_->GetDpiScale()); |
| return true; |
| } |
| |
| resize_status_ = ResizeState::kResizeStarted; |
| resize_target_width_ = width; |
| resize_target_height_ = height; |
| |
| SendWindowMetrics(width, height, binding_handler_->GetDpiScale()); |
| |
| // Block the platform thread until a frame is presented with the target |
| // size. See |OnFrameGenerated|, |OnEmptyFrameGenerated|, and |
| // |OnFramePresented|. |
| return resize_cv_.wait_for(lock, kWindowResizeTimeout, |
| [&resize_status = resize_status_] { |
| return resize_status == ResizeState::kDone; |
| }); |
| } |
| |
| void FlutterWindowsView::OnWindowRepaint() { |
| ForceRedraw(); |
| } |
| |
| void FlutterWindowsView::OnPointerMove(double x, |
| double y, |
| FlutterPointerDeviceKind device_kind, |
| int32_t device_id, |
| int modifiers_state) { |
| engine_->keyboard_key_handler()->SyncModifiersIfNeeded(modifiers_state); |
| SendPointerMove(x, y, GetOrCreatePointerState(device_kind, device_id)); |
| } |
| |
| void FlutterWindowsView::OnPointerDown( |
| double x, |
| double y, |
| FlutterPointerDeviceKind device_kind, |
| int32_t device_id, |
| FlutterPointerMouseButtons flutter_button) { |
| if (flutter_button != 0) { |
| auto state = GetOrCreatePointerState(device_kind, device_id); |
| state->buttons |= flutter_button; |
| SendPointerDown(x, y, state); |
| } |
| } |
| |
| void FlutterWindowsView::OnPointerUp( |
| double x, |
| double y, |
| FlutterPointerDeviceKind device_kind, |
| int32_t device_id, |
| FlutterPointerMouseButtons flutter_button) { |
| if (flutter_button != 0) { |
| auto state = GetOrCreatePointerState(device_kind, device_id); |
| state->buttons &= ~flutter_button; |
| SendPointerUp(x, y, state); |
| } |
| } |
| |
| void FlutterWindowsView::OnPointerLeave(double x, |
| double y, |
| FlutterPointerDeviceKind device_kind, |
| int32_t device_id) { |
| SendPointerLeave(x, y, GetOrCreatePointerState(device_kind, device_id)); |
| } |
| |
| void FlutterWindowsView::OnPointerPanZoomStart(int32_t device_id) { |
| PointerLocation point = binding_handler_->GetPrimaryPointerLocation(); |
| SendPointerPanZoomStart(device_id, point.x, point.y); |
| } |
| |
| void FlutterWindowsView::OnPointerPanZoomUpdate(int32_t device_id, |
| double pan_x, |
| double pan_y, |
| double scale, |
| double rotation) { |
| SendPointerPanZoomUpdate(device_id, pan_x, pan_y, scale, rotation); |
| } |
| |
| void FlutterWindowsView::OnPointerPanZoomEnd(int32_t device_id) { |
| SendPointerPanZoomEnd(device_id); |
| } |
| |
| void FlutterWindowsView::OnText(const std::u16string& text) { |
| SendText(text); |
| } |
| |
| void FlutterWindowsView::OnKey(int key, |
| int scancode, |
| int action, |
| char32_t character, |
| bool extended, |
| bool was_down, |
| KeyEventCallback callback) { |
| SendKey(key, scancode, action, character, extended, was_down, callback); |
| } |
| |
| void FlutterWindowsView::OnComposeBegin() { |
| SendComposeBegin(); |
| } |
| |
| void FlutterWindowsView::OnComposeCommit() { |
| SendComposeCommit(); |
| } |
| |
| void FlutterWindowsView::OnComposeEnd() { |
| SendComposeEnd(); |
| } |
| |
| void FlutterWindowsView::OnComposeChange(const std::u16string& text, |
| int cursor_pos) { |
| SendComposeChange(text, cursor_pos); |
| } |
| |
| void FlutterWindowsView::OnScroll(double x, |
| double y, |
| double delta_x, |
| double delta_y, |
| int scroll_offset_multiplier, |
| FlutterPointerDeviceKind device_kind, |
| int32_t device_id) { |
| SendScroll(x, y, delta_x, delta_y, scroll_offset_multiplier, device_kind, |
| device_id); |
| } |
| |
| void FlutterWindowsView::OnScrollInertiaCancel(int32_t device_id) { |
| PointerLocation point = binding_handler_->GetPrimaryPointerLocation(); |
| SendScrollInertiaCancel(device_id, point.x, point.y); |
| } |
| |
| void FlutterWindowsView::OnUpdateSemanticsEnabled(bool enabled) { |
| engine_->UpdateSemanticsEnabled(enabled); |
| } |
| |
| gfx::NativeViewAccessible FlutterWindowsView::GetNativeViewAccessible() { |
| if (!accessibility_bridge_) { |
| return nullptr; |
| } |
| |
| return accessibility_bridge_->GetChildOfAXFragmentRoot(); |
| } |
| |
| void FlutterWindowsView::OnCursorRectUpdated(const Rect& rect) { |
| binding_handler_->OnCursorRectUpdated(rect); |
| } |
| |
| void FlutterWindowsView::OnResetImeComposing() { |
| binding_handler_->OnResetImeComposing(); |
| } |
| |
| // Sends new size information to FlutterEngine. |
| void FlutterWindowsView::SendWindowMetrics(size_t width, |
| size_t height, |
| double pixel_ratio) const { |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = width; |
| event.height = height; |
| event.pixel_ratio = pixel_ratio; |
| event.view_id = view_id_; |
| engine_->SendWindowMetricsEvent(event); |
| } |
| |
| FlutterWindowMetricsEvent FlutterWindowsView::CreateWindowMetricsEvent() const { |
| PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds(); |
| double pixel_ratio = binding_handler_->GetDpiScale(); |
| |
| FlutterWindowMetricsEvent event = {}; |
| event.struct_size = sizeof(event); |
| event.width = bounds.width; |
| event.height = bounds.height; |
| event.pixel_ratio = pixel_ratio; |
| event.view_id = view_id_; |
| |
| return event; |
| } |
| |
| void FlutterWindowsView::SendInitialBounds() { |
| // Non-implicit views' initial window metrics are sent when the view is added |
| // to the engine. |
| if (!IsImplicitView()) { |
| return; |
| } |
| |
| engine_->SendWindowMetricsEvent(CreateWindowMetricsEvent()); |
| } |
| |
| FlutterWindowsView::PointerState* FlutterWindowsView::GetOrCreatePointerState( |
| FlutterPointerDeviceKind device_kind, |
| int32_t device_id) { |
| // Create a virtual pointer ID that is unique across all device types |
| // to prevent pointers from clashing in the engine's converter |
| // (lib/ui/window/pointer_data_packet_converter.cc) |
| int32_t pointer_id = (static_cast<int32_t>(device_kind) << 28) | device_id; |
| |
| auto [it, added] = pointer_states_.try_emplace(pointer_id, nullptr); |
| if (added) { |
| auto state = std::make_unique<PointerState>(); |
| state->device_kind = device_kind; |
| state->pointer_id = pointer_id; |
| it->second = std::move(state); |
| } |
| |
| return it->second.get(); |
| } |
| |
| // Set's |event_data|'s phase to either kMove or kHover depending on the current |
| // primary mouse button state. |
| void FlutterWindowsView::SetEventPhaseFromCursorButtonState( |
| FlutterPointerEvent* event_data, |
| const PointerState* state) const { |
| // For details about this logic, see FlutterPointerPhase in the embedder.h |
| // file. |
| if (state->buttons == 0) { |
| event_data->phase = state->flutter_state_is_down |
| ? FlutterPointerPhase::kUp |
| : FlutterPointerPhase::kHover; |
| } else { |
| event_data->phase = state->flutter_state_is_down |
| ? FlutterPointerPhase::kMove |
| : FlutterPointerPhase::kDown; |
| } |
| } |
| |
| void FlutterWindowsView::SendPointerMove(double x, |
| double y, |
| PointerState* state) { |
| FlutterPointerEvent event = {}; |
| event.x = x; |
| event.y = y; |
| |
| SetEventPhaseFromCursorButtonState(&event, state); |
| SendPointerEventWithData(event, state); |
| } |
| |
| void FlutterWindowsView::SendPointerDown(double x, |
| double y, |
| PointerState* state) { |
| FlutterPointerEvent event = {}; |
| event.x = x; |
| event.y = y; |
| |
| SetEventPhaseFromCursorButtonState(&event, state); |
| SendPointerEventWithData(event, state); |
| |
| state->flutter_state_is_down = true; |
| } |
| |
| void FlutterWindowsView::SendPointerUp(double x, |
| double y, |
| PointerState* state) { |
| FlutterPointerEvent event = {}; |
| event.x = x; |
| event.y = y; |
| |
| SetEventPhaseFromCursorButtonState(&event, state); |
| SendPointerEventWithData(event, state); |
| if (event.phase == FlutterPointerPhase::kUp) { |
| state->flutter_state_is_down = false; |
| } |
| } |
| |
| void FlutterWindowsView::SendPointerLeave(double x, |
| double y, |
| PointerState* state) { |
| FlutterPointerEvent event = {}; |
| event.x = x; |
| event.y = y; |
| event.phase = FlutterPointerPhase::kRemove; |
| SendPointerEventWithData(event, state); |
| } |
| |
| void FlutterWindowsView::SendPointerPanZoomStart(int32_t device_id, |
| double x, |
| double y) { |
| auto state = |
| GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id); |
| state->pan_zoom_start_x = x; |
| state->pan_zoom_start_y = y; |
| FlutterPointerEvent event = {}; |
| event.x = x; |
| event.y = y; |
| event.phase = FlutterPointerPhase::kPanZoomStart; |
| SendPointerEventWithData(event, state); |
| } |
| |
| void FlutterWindowsView::SendPointerPanZoomUpdate(int32_t device_id, |
| double pan_x, |
| double pan_y, |
| double scale, |
| double rotation) { |
| auto state = |
| GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id); |
| FlutterPointerEvent event = {}; |
| event.x = state->pan_zoom_start_x; |
| event.y = state->pan_zoom_start_y; |
| event.pan_x = pan_x; |
| event.pan_y = pan_y; |
| event.scale = scale; |
| event.rotation = rotation; |
| event.phase = FlutterPointerPhase::kPanZoomUpdate; |
| SendPointerEventWithData(event, state); |
| } |
| |
| void FlutterWindowsView::SendPointerPanZoomEnd(int32_t device_id) { |
| auto state = |
| GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id); |
| FlutterPointerEvent event = {}; |
| event.x = state->pan_zoom_start_x; |
| event.y = state->pan_zoom_start_y; |
| event.phase = FlutterPointerPhase::kPanZoomEnd; |
| SendPointerEventWithData(event, state); |
| } |
| |
| void FlutterWindowsView::SendText(const std::u16string& text) { |
| engine_->text_input_plugin()->TextHook(text); |
| } |
| |
| void FlutterWindowsView::SendKey(int key, |
| int scancode, |
| int action, |
| char32_t character, |
| bool extended, |
| bool was_down, |
| KeyEventCallback callback) { |
| engine_->keyboard_key_handler()->KeyboardHook( |
| key, scancode, action, character, extended, was_down, |
| [=, callback = std::move(callback)](bool handled) { |
| if (!handled) { |
| engine_->text_input_plugin()->KeyboardHook( |
| key, scancode, action, character, extended, was_down); |
| } |
| callback(handled); |
| }); |
| } |
| |
| void FlutterWindowsView::SendComposeBegin() { |
| engine_->text_input_plugin()->ComposeBeginHook(); |
| } |
| |
| void FlutterWindowsView::SendComposeCommit() { |
| engine_->text_input_plugin()->ComposeCommitHook(); |
| } |
| |
| void FlutterWindowsView::SendComposeEnd() { |
| engine_->text_input_plugin()->ComposeEndHook(); |
| } |
| |
| void FlutterWindowsView::SendComposeChange(const std::u16string& text, |
| int cursor_pos) { |
| engine_->text_input_plugin()->ComposeChangeHook(text, cursor_pos); |
| } |
| |
| void FlutterWindowsView::SendScroll(double x, |
| double y, |
| double delta_x, |
| double delta_y, |
| int scroll_offset_multiplier, |
| FlutterPointerDeviceKind device_kind, |
| int32_t device_id) { |
| auto state = GetOrCreatePointerState(device_kind, device_id); |
| |
| FlutterPointerEvent event = {}; |
| event.x = x; |
| event.y = y; |
| event.signal_kind = FlutterPointerSignalKind::kFlutterPointerSignalKindScroll; |
| event.scroll_delta_x = delta_x * scroll_offset_multiplier; |
| event.scroll_delta_y = delta_y * scroll_offset_multiplier; |
| SetEventPhaseFromCursorButtonState(&event, state); |
| SendPointerEventWithData(event, state); |
| } |
| |
| void FlutterWindowsView::SendScrollInertiaCancel(int32_t device_id, |
| double x, |
| double y) { |
| auto state = |
| GetOrCreatePointerState(kFlutterPointerDeviceKindTrackpad, device_id); |
| |
| FlutterPointerEvent event = {}; |
| event.x = x; |
| event.y = y; |
| event.signal_kind = |
| FlutterPointerSignalKind::kFlutterPointerSignalKindScrollInertiaCancel; |
| SetEventPhaseFromCursorButtonState(&event, state); |
| SendPointerEventWithData(event, state); |
| } |
| |
| void FlutterWindowsView::SendPointerEventWithData( |
| const FlutterPointerEvent& event_data, |
| PointerState* state) { |
| // If sending anything other than an add, and the pointer isn't already added, |
| // synthesize an add to satisfy Flutter's expectations about events. |
| if (!state->flutter_state_is_added && |
| event_data.phase != FlutterPointerPhase::kAdd) { |
| FlutterPointerEvent event = {}; |
| event.phase = FlutterPointerPhase::kAdd; |
| event.x = event_data.x; |
| event.y = event_data.y; |
| event.buttons = 0; |
| SendPointerEventWithData(event, state); |
| } |
| |
| // Don't double-add (e.g., if events are delivered out of order, so an add has |
| // already been synthesized). |
| if (state->flutter_state_is_added && |
| event_data.phase == FlutterPointerPhase::kAdd) { |
| return; |
| } |
| |
| FlutterPointerEvent event = event_data; |
| event.device_kind = state->device_kind; |
| event.device = state->pointer_id; |
| event.buttons = state->buttons; |
| event.view_id = view_id_; |
| |
| // Set metadata that's always the same regardless of the event. |
| event.struct_size = sizeof(event); |
| event.timestamp = |
| std::chrono::duration_cast<std::chrono::microseconds>( |
| std::chrono::high_resolution_clock::now().time_since_epoch()) |
| .count(); |
| |
| engine_->SendPointerEvent(event); |
| |
| if (event_data.phase == FlutterPointerPhase::kAdd) { |
| state->flutter_state_is_added = true; |
| } else if (event_data.phase == FlutterPointerPhase::kRemove) { |
| auto it = pointer_states_.find(state->pointer_id); |
| if (it != pointer_states_.end()) { |
| pointer_states_.erase(it); |
| } |
| } |
| } |
| |
| void FlutterWindowsView::OnFramePresented() { |
| // Called on the engine's raster thread. |
| std::unique_lock<std::mutex> lock(resize_mutex_); |
| |
| switch (resize_status_) { |
| case ResizeState::kResizeStarted: |
| // The caller must first call |OnFrameGenerated| or |
| // |OnEmptyFrameGenerated| before calling this method. This |
| // indicates one of the following: |
| // |
| // 1. The caller did not call these methods. |
| // 2. The caller ignored these methods' result. |
| // 3. The platform thread started a resize after the caller called these |
| // methods. We might have presented a frame of the wrong size to the |
| // view. |
| return; |
| case ResizeState::kFrameGenerated: { |
| // A frame was generated for a pending resize. |
| // Unblock the platform thread. |
| resize_status_ = ResizeState::kDone; |
| lock.unlock(); |
| resize_cv_.notify_all(); |
| |
| // Blocking the raster thread until DWM flushes alleviates glitches where |
| // previous size surface is stretched over current size view. |
| windows_proc_table_->DwmFlush(); |
| } |
| case ResizeState::kDone: |
| return; |
| } |
| } |
| |
| bool FlutterWindowsView::ClearSoftwareBitmap() { |
| return binding_handler_->OnBitmapSurfaceCleared(); |
| } |
| |
| bool FlutterWindowsView::PresentSoftwareBitmap(const void* allocation, |
| size_t row_bytes, |
| size_t height) { |
| return binding_handler_->OnBitmapSurfaceUpdated(allocation, row_bytes, |
| height); |
| } |
| |
| FlutterViewId FlutterWindowsView::view_id() const { |
| return view_id_; |
| } |
| |
| bool FlutterWindowsView::IsImplicitView() const { |
| return view_id_ == kImplicitViewId; |
| } |
| |
| void FlutterWindowsView::CreateRenderSurface() { |
| FML_DCHECK(surface_ == nullptr); |
| |
| if (engine_->egl_manager()) { |
| PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds(); |
| surface_ = engine_->egl_manager()->CreateWindowSurface( |
| GetWindowHandle(), bounds.width, bounds.height); |
| |
| UpdateVsync(*engine_, surface_.get(), NeedsVsync()); |
| |
| resize_target_width_ = bounds.width; |
| resize_target_height_ = bounds.height; |
| } |
| } |
| |
| bool FlutterWindowsView::ResizeRenderSurface(size_t width, size_t height) { |
| FML_DCHECK(surface_ != nullptr); |
| |
| // No-op if the surface is already the desired size. |
| if (width == surface_->width() && height == surface_->height()) { |
| return true; |
| } |
| |
| auto const existing_vsync = surface_->vsync_enabled(); |
| |
| // TODO: Destroying the surface and re-creating it is expensive. |
| // Ideally this would use ANGLE's automatic surface sizing instead. |
| // See: https://github.com/flutter/flutter/issues/79427 |
| if (!surface_->Destroy()) { |
| FML_LOG(ERROR) << "View resize failed to destroy surface"; |
| return false; |
| } |
| |
| std::unique_ptr<egl::WindowSurface> resized_surface = |
| engine_->egl_manager()->CreateWindowSurface(GetWindowHandle(), width, |
| height); |
| if (!resized_surface) { |
| FML_LOG(ERROR) << "View resize failed to create surface"; |
| return false; |
| } |
| |
| if (!resized_surface->MakeCurrent() || |
| !resized_surface->SetVSyncEnabled(existing_vsync)) { |
| // Surfaces block until the v-blank by default. |
| // Failing to update the vsync might result in unnecessary blocking. |
| // This regresses performance but not correctness. |
| FML_LOG(ERROR) << "View resize failed to set vsync"; |
| } |
| |
| surface_ = std::move(resized_surface); |
| return true; |
| } |
| |
| egl::WindowSurface* FlutterWindowsView::surface() const { |
| return surface_.get(); |
| } |
| |
| void FlutterWindowsView::OnHighContrastChanged() { |
| engine_->UpdateHighContrastMode(); |
| } |
| |
| HWND FlutterWindowsView::GetWindowHandle() const { |
| return binding_handler_->GetWindowHandle(); |
| } |
| |
| FlutterWindowsEngine* FlutterWindowsView::GetEngine() const { |
| return engine_; |
| } |
| |
| void FlutterWindowsView::AnnounceAlert(const std::wstring& text) { |
| auto alert_delegate = binding_handler_->GetAlertDelegate(); |
| if (!alert_delegate) { |
| return; |
| } |
| alert_delegate->SetText(fml::WideStringToUtf16(text)); |
| ui::AXPlatformNodeWin* alert_node = binding_handler_->GetAlert(); |
| NotifyWinEventWrapper(alert_node, ax::mojom::Event::kAlert); |
| } |
| |
| void FlutterWindowsView::NotifyWinEventWrapper(ui::AXPlatformNodeWin* node, |
| ax::mojom::Event event) { |
| if (node) { |
| node->NotifyAccessibilityEvent(event); |
| } |
| } |
| |
| ui::AXFragmentRootDelegateWin* FlutterWindowsView::GetAxFragmentRootDelegate() { |
| return accessibility_bridge_.get(); |
| } |
| |
| ui::AXPlatformNodeWin* FlutterWindowsView::AlertNode() const { |
| return binding_handler_->GetAlert(); |
| } |
| |
| std::shared_ptr<AccessibilityBridgeWindows> |
| FlutterWindowsView::CreateAccessibilityBridge() { |
| return std::make_shared<AccessibilityBridgeWindows>(this); |
| } |
| |
| void FlutterWindowsView::UpdateSemanticsEnabled(bool enabled) { |
| if (semantics_enabled_ != enabled) { |
| semantics_enabled_ = enabled; |
| |
| if (!semantics_enabled_ && accessibility_bridge_) { |
| accessibility_bridge_.reset(); |
| } else if (semantics_enabled_ && !accessibility_bridge_) { |
| accessibility_bridge_ = CreateAccessibilityBridge(); |
| } |
| } |
| } |
| |
| void FlutterWindowsView::OnDwmCompositionChanged() { |
| UpdateVsync(*engine_, surface_.get(), NeedsVsync()); |
| } |
| |
| void FlutterWindowsView::OnWindowStateEvent(HWND hwnd, WindowStateEvent event) { |
| engine_->OnWindowStateEvent(hwnd, event); |
| } |
| |
| bool FlutterWindowsView::NeedsVsync() const { |
| // If the Desktop Window Manager composition is enabled, |
| // the system itself synchronizes with vsync. |
| // See: https://learn.microsoft.com/windows/win32/dwm/composition-ovw |
| return !windows_proc_table_->DwmIsCompositionEnabled(); |
| } |
| |
| } // namespace flutter |