diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index f37b0279e3c8d..24db8a78eeb23 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2038,7 +2038,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/key_map.g.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/keyboard_binding.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/layers.dart + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/mouse/cursor.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/noto_font.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/noto_font_encoding.dart + ../../../flutter/LICENSE @@ -4787,7 +4787,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/js_interop/js_typed_data.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/key_map.g.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard_binding.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/layers.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/mouse/cursor.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/navigation/history.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/noto_font.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/noto_font_encoding.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 016117334272e..1c166b90649a9 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -111,7 +111,7 @@ export 'engine/js_interop/js_typed_data.dart'; export 'engine/key_map.g.dart'; export 'engine/keyboard_binding.dart'; export 'engine/layers.dart'; -export 'engine/mouse_cursor.dart'; +export 'engine/mouse/cursor.dart'; export 'engine/navigation/history.dart'; export 'engine/noto_font.dart'; export 'engine/noto_font_encoding.dart'; diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index c5b4411a97b70..b8be197e5301f 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -743,6 +743,7 @@ extension DomCSSStyleDeclarationExtension on DomCSSStyleDeclaration { set alignContent(String value) => setProperty('align-content', value); set textAlign(String value) => setProperty('text-align', value); set font(String value) => setProperty('font', value); + set cursor(String value) => setProperty('cursor', value); String get width => getPropertyValue('width'); String get height => getPropertyValue('height'); String get position => getPropertyValue('position'); @@ -807,6 +808,7 @@ extension DomCSSStyleDeclarationExtension on DomCSSStyleDeclaration { String get alignContent => getPropertyValue('align-content'); String get textAlign => getPropertyValue('text-align'); String get font => getPropertyValue('font'); + String get cursor => getPropertyValue('cursor'); @JS('getPropertyValue') external JSString _getPropertyValue(JSString property); diff --git a/lib/web_ui/lib/src/engine/initialization.dart b/lib/web_ui/lib/src/engine/initialization.dart index 5e94e5a78b77b..ed537132c98ca 100644 --- a/lib/web_ui/lib/src/engine/initialization.dart +++ b/lib/web_ui/lib/src/engine/initialization.dart @@ -226,7 +226,6 @@ Future initializeEngineUi() async { _initializationState = DebugEngineInitializationState.initializingUi; RawKeyboard.initialize(onMacOs: operatingSystem == OperatingSystem.macOs); - MouseCursor.initialize(); ensureFlutterViewEmbedderInitialized(); _initializationState = DebugEngineInitializationState.initialized; } diff --git a/lib/web_ui/lib/src/engine/mouse_cursor.dart b/lib/web_ui/lib/src/engine/mouse/cursor.dart similarity index 73% rename from lib/web_ui/lib/src/engine/mouse_cursor.dart rename to lib/web_ui/lib/src/engine/mouse/cursor.dart index 0ccfbed03b129..589891173b5fe 100644 --- a/lib/web_ui/lib/src/engine/mouse_cursor.dart +++ b/lib/web_ui/lib/src/engine/mouse/cursor.dart @@ -2,23 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'embedder.dart'; -import 'util.dart'; +import '../dom.dart'; -/// Provides mouse cursor bindings, such as the `flutter/mousecursor` channel. +/// Controls the mouse cursor in the given [element]. class MouseCursor { - MouseCursor._(); + MouseCursor(this.element); - /// Initializes the [MouseCursor] singleton. - /// - /// Use the [instance] getter to get the singleton after calling this method. - static void initialize() { - _instance ??= MouseCursor._(); - } - - /// The [MouseCursor] singleton. - static MouseCursor? get instance => _instance; - static MouseCursor? _instance; + final DomElement element; // Map from Flutter's kind values to CSS's cursor values. // @@ -61,15 +51,12 @@ class MouseCursor { 'zoomIn': 'zoom-in', 'zoomOut': 'zoom-out', }; + static String _mapKindToCssValue(String? kind) { return _kindToCssValueMap[kind] ?? 'default'; } void activateSystemCursor(String? kind) { - setElementStyle( - flutterViewEmbedder.flutterViewElement, - 'cursor', - _mapKindToCssValue(kind), - ); + element.style.cursor = _mapKindToCssValue(kind); } } diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 89bbab28f046b..424346aafd548 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -171,7 +171,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// * [PlatformDisptacher.views] for a list of all [FlutterView]s provided /// by the platform. @override - ui.FlutterView? get implicitView => viewData[kImplicitViewId]; + EngineFlutterWindow? get implicitView => viewData[kImplicitViewId] as EngineFlutterWindow?; /// A callback that is invoked whenever the platform's [devicePixelRatio], /// [physicalSize], [padding], [viewInsets], or [systemGestureInsets] @@ -505,10 +505,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { // TODO(a-wallen): As multi-window support expands, the pop call // will need to include the view ID. Right now only one view is // supported. - (viewData[kImplicitViewId]! as EngineFlutterWindow) - .browserHistory - .exit() - .then((_) { + implicitView!.browserHistory.exit().then((_) { replyToPlatformMessage( callback, codec.encodeSuccessEnvelope(true)); }); @@ -585,7 +582,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { final Map arguments = decoded.arguments as Map; switch (decoded.method) { case 'activateSystemCursor': - MouseCursor.instance!.activateSystemCursor(arguments.tryString('kind')); + implicitView!.mouseCursor.activateSystemCursor(arguments.tryString('kind')); } return; @@ -618,9 +615,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { // TODO(a-wallen): As multi-window support expands, the navigation call // will need to include the view ID. Right now only one view is // supported. - (viewData[kImplicitViewId]! as EngineFlutterWindow) - .handleNavigationMessage(data) - .then((bool handled) { + implicitView!.handleNavigationMessage(data).then((bool handled) { if (handled) { const MethodCodec codec = JSONMethodCodec(); replyToPlatformMessage(callback, codec.encodeSuccessEnvelope(true)); @@ -1231,8 +1226,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// requests from the embedder. @override String get defaultRouteName { - return _defaultRouteName ??= - (viewData[kImplicitViewId]! as EngineFlutterWindow).browserHistory.currentPath; + return _defaultRouteName ??= implicitView!.browserHistory.currentPath; } /// Lazily initialized when the `defaultRouteName` getter is invoked. diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 47a1215fd1f9b..0dd502def1627 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -16,6 +16,8 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web; import '../engine.dart' show DimensionsProvider, registerHotRestartListener, renderer; import 'display.dart'; import 'dom.dart'; +import 'embedder.dart'; +import 'mouse/cursor.dart'; import 'navigation/history.dart'; import 'platform_dispatcher.dart'; import 'services.dart'; @@ -29,8 +31,17 @@ const bool debugPrintPlatformMessages = false; /// The view ID for the implicit flutter view provided by the platform. const int kImplicitViewId = 0; +/// Represents all views in the Flutter Web Engine. +/// +/// In addition to everything defined in [ui.FlutterView], this class adds +/// a few web-specific properties. +abstract interface class EngineFlutterView extends ui.FlutterView { + MouseCursor get mouseCursor; + DomElement get rootElement; +} + /// The Web implementation of [ui.SingletonFlutterWindow]. -class EngineFlutterWindow extends ui.SingletonFlutterWindow { +class EngineFlutterWindow extends ui.SingletonFlutterWindow implements EngineFlutterView { EngineFlutterWindow(this.viewId, this.platformDispatcher) { platformDispatcher.viewData[viewId] = this; platformDispatcher.windowConfigurations[viewId] = const ViewConfiguration(); @@ -53,6 +64,12 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow { @override final EnginePlatformDispatcher platformDispatcher; + @override + late final MouseCursor mouseCursor = MouseCursor(rootElement); + + @override + DomElement get rootElement => flutterViewEmbedder.flutterViewElement; + /// Handles the browser history integration to allow users to use the back /// button, etc. BrowserHistory get browserHistory { diff --git a/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart b/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart index 8f6d74cc09469..f4adbc5398af2 100644 --- a/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart +++ b/lib/web_ui/test/canvaskit/initialization/services_vs_ui_test.dart @@ -17,7 +17,6 @@ void testMain() { expect(findGlassPane(), isNull); expect(RawKeyboard.instance, isNull); - expect(MouseCursor.instance, isNull); expect(KeyboardBinding.instance, isNull); expect(PointerBinding.instance, isNull); @@ -28,7 +27,6 @@ void testMain() { expect(findGlassPane(), isNull); expect(RawKeyboard.instance, isNull); - expect(MouseCursor.instance, isNull); expect(KeyboardBinding.instance, isNull); expect(PointerBinding.instance, isNull); @@ -36,7 +34,6 @@ void testMain() { await initializeEngineUi(); expect(findGlassPane(), isNotNull); expect(RawKeyboard.instance, isNotNull); - expect(MouseCursor.instance, isNotNull); expect(KeyboardBinding.instance, isNotNull); expect(PointerBinding.instance, isNotNull); }); diff --git a/lib/web_ui/test/engine/mouse/cursor_test.dart b/lib/web_ui/test/engine/mouse/cursor_test.dart new file mode 100644 index 0000000000000..f81012ba61efa --- /dev/null +++ b/lib/web_ui/test/engine/mouse/cursor_test.dart @@ -0,0 +1,40 @@ +// 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. + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +void testMain() { + group('$MouseCursor', () { + test('sets correct `cursor` style on root element', () { + final DomElement rootViewElement = createDomElement('div'); + final MouseCursor mouseCursor = MouseCursor(rootViewElement); + + mouseCursor.activateSystemCursor('alias'); + expect(rootViewElement.style.cursor, 'alias'); + + mouseCursor.activateSystemCursor('move'); + expect(rootViewElement.style.cursor, 'move'); + + mouseCursor.activateSystemCursor('precise'); + expect(rootViewElement.style.cursor, 'crosshair'); + + mouseCursor.activateSystemCursor('resizeDownRight'); + expect(rootViewElement.style.cursor, 'se-resize'); + }); + + test('handles unknown cursor type', () { + final DomElement rootViewElement = createDomElement('div'); + final MouseCursor mouseCursor = MouseCursor(rootViewElement); + + mouseCursor.activateSystemCursor('unknown'); + expect(rootViewElement.style.cursor, 'default'); + }); + }); +}