diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index bfad954..e2f87e0 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -8,7 +8,7 @@ jobs: analysis: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Fetch Flutter SDK run: | @@ -21,7 +21,7 @@ jobs: run: flutter/bin/flutter pub get - name: Verify formatting - run: flutter/bin/dart format --output=none --set-exit-if-changed lib + run: flutter/bin/dart format --output=none --set-exit-if-changed lib packages - name: Analyze project source run: flutter/bin/dart analyze --fatal-warnings lib diff --git a/packages/analysis_options.yaml b/packages/analysis_options.yaml new file mode 100644 index 0000000..3f0e828 --- /dev/null +++ b/packages/analysis_options.yaml @@ -0,0 +1,9 @@ +# Take our settings from the repo's main analysis_options.yaml file, and include +# additional rules that are specific to production code. + +include: ../analysis_options.yaml + +linter: + rules: + - public_member_api_docs # see https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#documentation-dartdocs-javadocs-etc + - no_runtimeType_toString # use objectRuntimeType from package:foundation diff --git a/packages/flutter_elinux/CHANGELOG.md b/packages/flutter_elinux/CHANGELOG.md new file mode 100644 index 0000000..34c91b8 --- /dev/null +++ b/packages/flutter_elinux/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +* Initial commit diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/README.md b/packages/flutter_elinux/examples/platform_views/native_texture_view/README.md new file mode 100644 index 0000000..8f3f786 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/README.md @@ -0,0 +1,13 @@ +# native_texture_view + +This is an example flutter-elinux project for native texture view using platform views. + +## Getting Started + +Please replace the flutter_elinux's `path` in `pubspec.yaml`: + +``` + flutter_elinux: + # You need to correctly replace the path below with the path of your flutter-elinux directory. + path: /home/hidenori/work/flutter/flutter-elinux/packages/flutter_elinux +``` diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/analysis_options.yaml b/packages/flutter_elinux/examples/platform_views/native_texture_view/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/.gitignore b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/.gitignore new file mode 100644 index 0000000..229c109 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral/ diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/CMakeLists.txt b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/CMakeLists.txt new file mode 100644 index 0000000..95bc271 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.15) +# stop cmake from taking make from CMAKE_SYSROOT +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +project(runner LANGUAGES CXX) + +set(BINARY_NAME "texture") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Basically we use this include when we got the following error: +# fatal error: 'bits/c++config.h' file not found +include_directories(SYSTEM ${FLUTTER_SYSTEM_INCLUDE_DIRECTORIES}) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +# Configure build options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Configure build option to target backend. +if (NOT FLUTTER_TARGET_BACKEND_TYPE) + set(FLUTTER_TARGET_BACKEND_TYPE "wayland" CACHE + STRING "Flutter target backend type" FORCE) + set_property(CACHE FLUTTER_TARGET_BACKEND_TYPE PROPERTY STRINGS + "wayland" "gbm" "eglstream" "x11") +endif() + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +install(FILES "${FLUTTER_EMBEDDER_LIBRARY}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/CMakeLists.txt b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/CMakeLists.txt new file mode 100644 index 0000000..f141a3c --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/CMakeLists.txt @@ -0,0 +1,108 @@ +cmake_minimum_required(VERSION 3.15) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_engine.so") +if(FLUTTER_TARGET_BACKEND_TYPE MATCHES "gbm") + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_gbm.so") +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "eglstream") + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_eglstream.so") +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "x11") + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_x11.so") +else() + set(FLUTTER_EMBEDDER_LIBRARY "${EPHEMERAL_DIR}/libflutter_elinux_wayland.so") +endif() + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_EMBEDDER_LIBRARY ${FLUTTER_EMBEDDER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/elinux/" PARENT_SCOPE) +set(AOT_LIBRARY "${EPHEMERAL_DIR}/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_plugin_registrar.h" + "flutter_messenger.h" + "flutter_texture_registrar.h" + "flutter_elinux.h" + "flutter_platform_views.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE "${FLUTTER_EMBEDDER_LIBRARY}") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list_prepend(CPP_WRAPPER_SOURCES_CORE "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list_prepend(CPP_WRAPPER_SOURCES_PLUGIN "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list_prepend(CPP_WRAPPER_SOURCES_APP "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + "${FLUTTER_EMBEDDER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/generated_plugin_registrant.cc b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..1ccfa30 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + NativeTextureViewPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("NativeTextureViewPlugin")); +} diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/generated_plugin_registrant.h b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..a31c23c --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/generated_plugin_registrant.h @@ -0,0 +1,13 @@ +// +// Generated file. Do not edit. +// + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/generated_plugins.cmake b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..0ca3827 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/flutter/generated_plugins.cmake @@ -0,0 +1,16 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + native_texture_view +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/elinux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/CMakeLists.txt b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/CMakeLists.txt new file mode 100644 index 0000000..d15d5ca --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.15) +project(runner LANGUAGES CXX) + +if(FLUTTER_TARGET_BACKEND_TYPE MATCHES "gbm") + add_definitions(-DFLUTTER_TARGET_BACKEND_GBM) +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "eglstream") + add_definitions(-DFLUTTER_TARGET_BACKEND_EGLSTREAM) +elseif(FLUTTER_TARGET_BACKEND_TYPE MATCHES "x11") + add_definitions(-DFLUTTER_TARGET_BACKEND_X11) +else() + add_definitions(-DFLUTTER_TARGET_BACKEND_WAYLAND) +endif() + +add_executable(${BINARY_NAME} + "flutter_window.cc" + "main.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) +apply_standard_settings(${BINARY_NAME}) +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/command_options.h b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/command_options.h new file mode 100644 index 0000000..b0de931 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/command_options.h @@ -0,0 +1,402 @@ +// Copyright 2022 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMMAND_OPTIONS_ +#define COMMAND_OPTIONS_ + +#include +#include +#include +#include +#include +#include +#include + +namespace commandline { + +namespace { +constexpr char kOptionStyleNormal[] = "--"; +constexpr char kOptionStyleShort[] = "-"; +constexpr char kOptionValueForHelpMessage[] = "="; +} // namespace + +class Exception : public std::exception { + public: + Exception(const std::string& msg) : msg_(msg) {} + ~Exception() throw() {} + + const char* what() const throw() { return msg_.c_str(); } + + private: + std::string msg_; +}; + +class CommandOptions { + public: + CommandOptions() = default; + ~CommandOptions() = default; + + void AddWithoutValue(const std::string& name, + const std::string& short_name, + const std::string& description, + bool required) { + Add(name, short_name, description, "", + ReaderString(), required, false); + } + + void AddInt(const std::string& name, + const std::string& short_name, + const std::string& description, + const int& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderInt(), required, true); + } + + void AddDouble(const std::string& name, + const std::string& short_name, + const std::string& description, + const double& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderDouble(), required, true); + } + + void AddString(const std::string& name, + const std::string& short_name, + const std::string& description, + const std::string& default_value, + bool required) { + Add(name, short_name, description, default_value, + ReaderString(), required, true); + } + + template + void Add(const std::string& name, + const std::string& short_name, + const std::string& description, + const T default_value, + F reader = F(), + bool required = true, + bool required_value = true) { + if (options_.find(name) != options_.end()) { + std::cerr << "Already registered option: " << name << std::endl; + return; + } + + if (lut_short_options_.find(short_name) != lut_short_options_.end()) { + std::cerr << short_name << "is already registered" << std::endl; + return; + } + lut_short_options_[short_name] = name; + + options_[name] = std::make_unique>( + name, short_name, description, default_value, reader, required, + required_value); + + // register to show help message. + registration_order_options_.push_back(options_[name].get()); + } + + bool Exist(const std::string& name) { + auto itr = options_.find(name); + return itr != options_.end() && itr->second->HasValue(); + } + + template + const T& GetValue(const std::string& name) { + auto itr = options_.find(name); + if (itr == options_.end()) { + throw Exception("Not found: " + name); + } + + auto* option_value = dynamic_cast*>(itr->second.get()); + if (!option_value) { + throw Exception("Type mismatch: " + name); + } + return option_value->GetValue(); + } + + bool Parse(int argc, const char* const* argv) { + if (argc < 1) { + errors_.push_back("No options"); + return false; + } + + command_name_ = argv[0]; + for (auto i = 1; i < argc; i++) { + const std::string arg(argv[i]); + + // normal options: e.g. --bundle=/data/sample/bundle --fullscreen + if (arg.length() > 2 && + arg.substr(0, 2).compare(kOptionStyleNormal) == 0) { + const size_t option_value_len = arg.find("=") != std::string::npos + ? (arg.length() - arg.find("=")) + : 0; + const bool has_value = option_value_len != 0; + std::string option_name = + arg.substr(2, arg.length() - 2 - option_value_len); + + if (options_.find(option_name) == options_.end()) { + errors_.push_back("Not found option: " + option_name); + continue; + } + + if (!has_value && options_[option_name]->IsRequiredValue()) { + errors_.push_back(option_name + " requres an option value"); + continue; + } + + if (has_value && !options_[option_name]->IsRequiredValue()) { + errors_.push_back(option_name + " doesn't requres an option value"); + continue; + } + + if (has_value) { + SetOptionValue(option_name, arg.substr(arg.find("=") + 1)); + } else { + SetOption(option_name); + } + } + // short options: e.g. -f /foo/file.txt -h 640 -abc + else if (arg.length() > 1 && + arg.substr(0, 1).compare(kOptionStyleShort) == 0) { + for (size_t j = 1; j < arg.length(); j++) { + const std::string option_name{argv[i][j]}; + + if (lut_short_options_.find(option_name) == + lut_short_options_.end()) { + errors_.push_back("Not found short option: " + option_name); + break; + } + + if (j == arg.length() - 1 && + options_[lut_short_options_[option_name]]->IsRequiredValue()) { + if (i == argc - 1) { + errors_.push_back("Invalid format option: " + option_name); + break; + } + SetOptionValue(lut_short_options_[option_name], argv[++i]); + } else { + SetOption(lut_short_options_[option_name]); + } + } + } else { + errors_.push_back("Invalid format option: " + arg); + } + } + + for (size_t i = 0; i < registration_order_options_.size(); i++) { + if (registration_order_options_[i]->IsRequired() && + !registration_order_options_[i]->HasValue()) { + errors_.push_back( + std::string(registration_order_options_[i]->GetName()) + + " option is mandatory."); + } + } + + return errors_.size() == 0; + } + + std::string GetError() { return errors_.size() > 0 ? errors_[0] : ""; } + + std::vector& GetErrors() { return errors_; } + + std::string ShowHelp() { + std::ostringstream ostream; + + ostream << "Usage: " << command_name_ << " "; + for (size_t i = 0; i < registration_order_options_.size(); i++) { + if (registration_order_options_[i]->IsRequired()) { + ostream << registration_order_options_[i]->GetHelpShortMessage() << " "; + } + } + ostream << std::endl; + + ostream << "Global options:" << std::endl; + size_t max_name_len = 0; + for (size_t i = 0; i < registration_order_options_.size(); i++) { + max_name_len = std::max( + max_name_len, registration_order_options_[i]->GetName().length()); + } + + for (size_t i = 0; i < registration_order_options_.size(); i++) { + if (!registration_order_options_[i]->GetShortName().empty()) { + ostream << kOptionStyleShort + << registration_order_options_[i]->GetShortName() << ", "; + } else { + ostream << std::string(4, ' '); + } + + size_t index_adjust = 0; + constexpr int kSpacerNum = 10; + auto need_value = registration_order_options_[i]->IsRequiredValue(); + ostream << kOptionStyleNormal + << registration_order_options_[i]->GetName(); + if (need_value) { + ostream << kOptionValueForHelpMessage; + index_adjust += std::string(kOptionValueForHelpMessage).length(); + } + ostream << std::string( + max_name_len + kSpacerNum - index_adjust - + registration_order_options_[i]->GetName().length(), + ' '); + ostream << registration_order_options_[i]->GetDescription() << std::endl; + } + + return ostream.str(); + } + + private: + struct ReaderInt { + int operator()(const std::string& value) { return std::stoi(value); } + }; + + struct ReaderString { + std::string operator()(const std::string& value) { return value; } + }; + + struct ReaderDouble { + double operator()(const std::string& value) { return std::stod(value); } + }; + + class Option { + public: + Option(const std::string& name, + const std::string& short_name, + const std::string& description, + bool required, + bool required_value) + : name_(name), + short_name_(short_name), + description_(description), + is_required_(required), + is_required_value_(required_value), + value_set_(false){}; + virtual ~Option() = default; + + const std::string& GetName() const { return name_; }; + + const std::string& GetShortName() const { return short_name_; }; + + const std::string& GetDescription() const { return description_; }; + + const std::string GetHelpShortMessage() const { + std::string message = kOptionStyleNormal + name_; + if (is_required_value_) { + message += kOptionValueForHelpMessage; + } + return message; + } + + bool IsRequired() const { return is_required_; }; + + bool IsRequiredValue() const { return is_required_value_; }; + + void Set() { value_set_ = true; }; + + virtual bool SetValue(const std::string& value) = 0; + + virtual bool HasValue() const = 0; + + protected: + std::string name_; + std::string short_name_; + std::string description_; + bool is_required_; + bool is_required_value_; + bool value_set_; + }; + + template + class OptionValue : public Option { + public: + OptionValue(const std::string& name, + const std::string& short_name, + const std::string& description, + const T& default_value, + bool required, + bool required_value) + : Option(name, short_name, description, required, required_value), + default_value_(default_value), + value_(default_value){}; + virtual ~OptionValue() = default; + + bool SetValue(const std::string& value) { + value_ = Read(value); + value_set_ = true; + return true; + } + + bool HasValue() const { return value_set_; } + + const T& GetValue() const { return value_; } + + protected: + virtual T Read(const std::string& s) = 0; + + T default_value_; + T value_; + }; + + template + class OptionValueReader : public OptionValue { + public: + OptionValueReader(const std::string& name, + const std::string& short_name, + const std::string& description, + const T default_value, + F reader, + bool required, + bool required_value) + : OptionValue(name, + short_name, + description, + default_value, + required, + required_value), + reader_(reader) {} + ~OptionValueReader() = default; + + private: + T Read(const std::string& value) { return reader_(value); } + + F reader_; + }; + + bool SetOption(const std::string& name) { + auto itr = options_.find(name); + if (itr == options_.end()) { + errors_.push_back("Unknown option: " + name); + return false; + } + + itr->second->Set(); + return true; + } + + bool SetOptionValue(const std::string& name, const std::string& value) { + auto itr = options_.find(name); + if (itr == options_.end()) { + errors_.push_back("Unknown option: " + name); + return false; + } + + if (!itr->second->SetValue(value)) { + errors_.push_back("Invalid option value: " + name + " = " + value); + return false; + } + return true; + } + + std::string command_name_; + std::unordered_map> options_; + std::unordered_map lut_short_options_; + std::vector registration_order_options_; + std::vector errors_; +}; + +} // namespace commandline + +#endif // COMMAND_OPTIONS_ diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/flutter_embedder_options.h b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/flutter_embedder_options.h new file mode 100644 index 0000000..41d0bd1 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/flutter_embedder_options.h @@ -0,0 +1,203 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_EMBEDDER_OPTIONS_ +#define FLUTTER_EMBEDDER_OPTIONS_ + +#include + +#include + +#include "command_options.h" + +class FlutterEmbedderOptions { + public: + FlutterEmbedderOptions() { + options_.AddString("bundle", "b", "Path to Flutter project bundle", + "./bundle", true); + options_.AddWithoutValue("no-cursor", "n", "No mouse cursor/pointer", + false); + options_.AddInt("rotation", "r", + "Window rotation(degree) [0(default)|90|180|270]", 0, + false); + options_.AddDouble("text-scaling-factor", "x", "Text scaling factor", 1.0, + false); + options_.AddWithoutValue("enable-high-contrast", "i", + "Request that UI be rendered with darker colors.", + false); + options_.AddDouble("force-scale-factor", "s", + "Force a scale factor instead using default value", 1.0, + false); + options_.AddWithoutValue( + "async-vblank", "v", + "Don't sync to compositor redraw/vblank (eglSwapInterval 0)", false); + +#if defined(FLUTTER_TARGET_BACKEND_GBM) || \ + defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) + // no more options. +#elif defined(FLUTTER_TARGET_BACKEND_X11) + options_.AddString("title", "t", "Window title", "Flutter", false); + options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", + false); + options_.AddInt("width", "w", "Window width", 1280, false); + options_.AddInt("height", "h", "Window height", 720, false); +#else // FLUTTER_TARGET_BACKEND_WAYLAND + options_.AddString("title", "t", "Window title", "Flutter", false); + options_.AddString("app-id", "a", "XDG App ID", "dev.flutter.elinux", + false); + options_.AddWithoutValue("onscreen-keyboard", "k", + "Enable on-screen keyboard", false); + options_.AddWithoutValue("window-decoration", "d", + "Enable window decorations", false); + options_.AddWithoutValue("fullscreen", "f", "Always full-screen display", + false); + options_.AddInt("width", "w", "Window width", 1280, false); + options_.AddInt("height", "h", "Window height", 720, false); +#endif + } + ~FlutterEmbedderOptions() = default; + + bool Parse(int argc, char** argv) { + if (!options_.Parse(argc, argv)) { + std::cerr << options_.GetError() << std::endl; + std::cout << options_.ShowHelp(); + return false; + } + + bundle_path_ = options_.GetValue("bundle"); + use_mouse_cursor_ = !options_.Exist("no-cursor"); + if (options_.Exist("rotation")) { + switch (options_.GetValue("rotation")) { + case 90: + window_view_rotation_ = + flutter::FlutterViewController::ViewRotation::kRotation_90; + break; + case 180: + window_view_rotation_ = + flutter::FlutterViewController::ViewRotation::kRotation_180; + break; + case 270: + window_view_rotation_ = + flutter::FlutterViewController::ViewRotation::kRotation_270; + break; + default: + window_view_rotation_ = + flutter::FlutterViewController::ViewRotation::kRotation_0; + break; + } + } + + text_scale_factor_ = options_.GetValue("text-scaling-factor"); + enable_high_contrast_ = options_.Exist("enable-high-contrast"); + + if (options_.Exist("force-scale-factor")) { + is_force_scale_factor_ = true; + scale_factor_ = options_.GetValue("force-scale-factor"); + } else { + is_force_scale_factor_ = false; + scale_factor_ = 1.0; + } + + enable_vsync_ = !options_.Exist("async-vblank"); + +#if defined(FLUTTER_TARGET_BACKEND_GBM) || \ + defined(FLUTTER_TARGET_BACKEND_EGLSTREAM) + use_onscreen_keyboard_ = false; + use_window_decoration_ = false; + window_view_mode_ = flutter::FlutterViewController::ViewMode::kFullscreen; +#elif defined(FLUTTER_TARGET_BACKEND_X11) + use_onscreen_keyboard_ = false; + use_window_decoration_ = false; + window_title_ = options_.GetValue("title"); + window_view_mode_ = + options_.Exist("fullscreen") + ? flutter::FlutterViewController::ViewMode::kFullscreen + : flutter::FlutterViewController::ViewMode::kNormal; + window_width_ = options_.GetValue("width"); + window_height_ = options_.GetValue("height"); +#else // FLUTTER_TARGET_BACKEND_WAYLAND + window_title_ = options_.GetValue("title"); + window_app_id_ = options_.GetValue("app-id"); + use_onscreen_keyboard_ = options_.Exist("onscreen-keyboard"); + use_window_decoration_ = options_.Exist("window-decoration"); + window_view_mode_ = + options_.Exist("fullscreen") + ? flutter::FlutterViewController::ViewMode::kFullscreen + : flutter::FlutterViewController::ViewMode::kNormal; + window_width_ = options_.GetValue("width"); + window_height_ = options_.GetValue("height"); +#endif + + return true; + } + + std::string BundlePath() const { + return bundle_path_; + } + std::string WindowTitle() const { + return window_title_; + } + std::string WindowAppID() const { + return window_app_id_; + } + bool IsUseMouseCursor() const { + return use_mouse_cursor_; + } + bool IsUseOnscreenKeyboard() const { + return use_onscreen_keyboard_; + } + bool IsUseWindowDecoraation() const { + return use_window_decoration_; + } + flutter::FlutterViewController::ViewMode WindowViewMode() const { + return window_view_mode_; + } + int WindowWidth() const { + return window_width_; + } + int WindowHeight() const { + return window_height_; + } + flutter::FlutterViewController::ViewRotation WindowRotation() const { + return window_view_rotation_; + } + double TextScaleFactor() const { + return text_scale_factor_; + } + bool EnableHighContrast() const { + return enable_high_contrast_; + } + bool IsForceScaleFactor() const { + return is_force_scale_factor_; + } + double ScaleFactor() const { + return scale_factor_; + } + bool EnableVsync() const { + return enable_vsync_; + } + + private: + commandline::CommandOptions options_; + + std::string bundle_path_; + std::string window_title_; + std::string window_app_id_; + bool use_mouse_cursor_ = true; + bool use_onscreen_keyboard_ = false; + bool use_window_decoration_ = false; + flutter::FlutterViewController::ViewMode window_view_mode_ = + flutter::FlutterViewController::ViewMode::kNormal; + int window_width_ = 1280; + int window_height_ = 720; + flutter::FlutterViewController::ViewRotation window_view_rotation_ = + flutter::FlutterViewController::ViewRotation::kRotation_0; + bool is_force_scale_factor_; + double scale_factor_; + double text_scale_factor_; + bool enable_high_contrast_; + bool enable_vsync_; +}; + +#endif // FLUTTER_EMBEDDER_OPTIONS_ diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/flutter_window.cc b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/flutter_window.cc new file mode 100644 index 0000000..0c5b639 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/flutter_window.cc @@ -0,0 +1,79 @@ +// Copyright 2021 Sony Corporation. 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_window.h" + +#include +#include +#include +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow( + const flutter::FlutterViewController::ViewProperties view_properties, + const flutter::DartProject project) + : view_properties_(view_properties), project_(project) {} + +bool FlutterWindow::OnCreate() { + flutter_view_controller_ = std::make_unique( + view_properties_, project_); + + // Ensure that basic setup of the controller was successful. + if (!flutter_view_controller_->engine() || + !flutter_view_controller_->view()) { + return false; + } + + // Register Flutter plugins. + RegisterPlugins(flutter_view_controller_->engine()); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_view_controller_) { + flutter_view_controller_ = nullptr; + } +} + +void FlutterWindow::Run() { + // Main loop. + auto next_flutter_event_time = + std::chrono::steady_clock::time_point::clock::now(); + while (flutter_view_controller_->view()->DispatchEvent()) { + // Wait until the next event. + { + auto wait_duration = + std::max(std::chrono::nanoseconds(0), + next_flutter_event_time - + std::chrono::steady_clock::time_point::clock::now()); + std::this_thread::sleep_for( + std::chrono::duration_cast(wait_duration)); + } + + // Processes any pending events in the Flutter engine, and returns the + // number of nanoseconds until the next scheduled event (or max, if none). + auto wait_duration = flutter_view_controller_->engine()->ProcessMessages(); + { + auto next_event_time = std::chrono::steady_clock::time_point::max(); + if (wait_duration != std::chrono::nanoseconds::max()) { + next_event_time = + std::min(next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + wait_duration); + } else { + // Wait for the next frame if no events. + auto frame_rate = flutter_view_controller_->view()->GetFrameRate(); + next_event_time = std::min( + next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + std::chrono::milliseconds( + static_cast(std::trunc(1000000.0 / frame_rate)))); + } + next_flutter_event_time = + std::max(next_flutter_event_time, next_event_time); + } + } +} diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/flutter_window.h b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/flutter_window.h new file mode 100644 index 0000000..20b9cb8 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/flutter_window.h @@ -0,0 +1,34 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_WINDOW_ +#define FLUTTER_WINDOW_ + +#include +#include + +#include + +class FlutterWindow { + public: + explicit FlutterWindow( + const flutter::FlutterViewController::ViewProperties view_properties, + const flutter::DartProject project); + ~FlutterWindow() = default; + + // Prevent copying. + FlutterWindow(FlutterWindow const&) = delete; + FlutterWindow& operator=(FlutterWindow const&) = delete; + + bool OnCreate(); + void OnDestroy(); + void Run(); + + private: + flutter::FlutterViewController::ViewProperties view_properties_; + flutter::DartProject project_; + std::unique_ptr flutter_view_controller_; +}; + +#endif // FLUTTER_WINDOW_ diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/main.cc b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/main.cc new file mode 100644 index 0000000..579daee --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/elinux/runner/main.cc @@ -0,0 +1,53 @@ +// Copyright 2021 Sony Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include +#include +#include + +#include "flutter_embedder_options.h" +#include "flutter_window.h" + +int main(int argc, char** argv) { + FlutterEmbedderOptions options; + if (!options.Parse(argc, argv)) { + return 0; + } + + // Creates the Flutter project. + const auto bundle_path = options.BundlePath(); + const std::wstring fl_path(bundle_path.begin(), bundle_path.end()); + flutter::DartProject project(fl_path); + auto command_line_arguments = std::vector(); + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + flutter::FlutterViewController::ViewProperties view_properties = {}; + view_properties.width = options.WindowWidth(); + view_properties.height = options.WindowHeight(); + view_properties.view_mode = options.WindowViewMode(); + view_properties.view_rotation = options.WindowRotation(); + view_properties.title = options.WindowTitle(); + view_properties.app_id = options.WindowAppID(); + view_properties.use_mouse_cursor = options.IsUseMouseCursor(); + view_properties.use_onscreen_keyboard = options.IsUseOnscreenKeyboard(); + view_properties.use_window_decoration = options.IsUseWindowDecoraation(); + view_properties.text_scale_factor = options.TextScaleFactor(); + view_properties.enable_high_contrast = options.EnableHighContrast(); + view_properties.force_scale_factor = options.IsForceScaleFactor(); + view_properties.scale_factor = options.ScaleFactor(); + view_properties.enable_vsync = options.EnableVsync(); + + // The Flutter instance hosted by this window. + FlutterWindow window(view_properties, project); + if (!window.OnCreate()) { + return 0; + } + window.Run(); + window.OnDestroy(); + + return 0; +} diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/lib/main.dart b/packages/flutter_elinux/examples/platform_views/native_texture_view/lib/main.dart new file mode 100644 index 0000000..c3ba5b0 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/lib/main.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:native_texture_view_example/native_texture_view.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'flutter-elinux platform views', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: const MyHomePage(title: 'Native view example using platform views'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text(widget.title), + ), + body: const Center(child: NativeTextureViewWidget()), + ); + } +} diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/lib/native_texture_view.dart b/packages/flutter_elinux/examples/platform_views/native_texture_view/lib/native_texture_view.dart new file mode 100644 index 0000000..608558e --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/lib/native_texture_view.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; + +import 'package:flutter_elinux/widgets.dart'; + +class NativeTextureViewWidget extends StatelessWidget { + const NativeTextureViewWidget({super.key}); + + @override + Widget build(BuildContext context) { + const String viewType = 'plugins.flutter.io/native_texture_view'; + const Map creationParams = {}; + + return PlatformViewLink( + viewType: viewType, + surfaceFactory: (context, controller) { + return ELinuxViewSurface( + controller: controller as ELinuxViewController, + gestureRecognizers: const >{}, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + ); + }, + onCreatePlatformView: (params) { + return PlatformViewsServiceELinux.initELinuxView( + id: params.id, + viewType: viewType, + layoutDirection: TextDirection.ltr, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + onFocus: () { + params.onFocusChanged(true); + }, + ) + ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) + ..create(); + }, + ); + } +} diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/.gitignore b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/.gitignore new file mode 100644 index 0000000..f68e1be --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/.gitignore @@ -0,0 +1 @@ +flutter/ diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/CMakeLists.txt b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/CMakeLists.txt new file mode 100644 index 0000000..90493ad --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.15) +set(PROJECT_NAME "native_texture_view") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "native_texture_view_plugin") + +find_package(PkgConfig) +pkg_check_modules(GLIB REQUIRED glib-2.0) +pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0) +if(USE_EGL_IMAGE_DMABUF) +pkg_check_modules(GSTREAMER_GL REQUIRED gstreamer-gl-1.0) +endif() + +add_library(${PLUGIN_NAME} SHARED + "native_texture_view_plugin.cc" + "native_texture_view_factory.cc" + "native_texture_view.cc" +) +apply_standard_settings(${PLUGIN_NAME}) +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) + +target_include_directories(${PLUGIN_NAME} + PRIVATE + ${GLIB_INCLUDE_DIRS} + ${GSTREAMER_INCLUDE_DIRS} +) +if(USE_EGL_IMAGE_DMABUF) +target_include_directories(${PLUGIN_NAME} + PRIVATE + ${GSTREAMER_GL_INCLUDE_DIRS} +) +endif() + +target_link_libraries(${PLUGIN_NAME} + PRIVATE + ${GLIB_LIBRARIES} + ${GSTREAMER_LIBRARIES} +) +if(USE_EGL_IMAGE_DMABUF) +target_link_libraries(${PLUGIN_NAME} + PRIVATE + ${GSTREAMER_GL_LIBRARIES} +) +endif() + +# List of absolute paths to libraries that should be bundled with the plugin +set(native_texture_view_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/include/native_texture_view/native_texture_view_plugin.h b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/include/native_texture_view/native_texture_view_plugin.h new file mode 100644 index 0000000..747a6a8 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/include/native_texture_view/native_texture_view_plugin.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_PLUGIN_NATIVE_TEXTURE_VIEW_PLUGIN_H_ +#define FLUTTER_PLUGIN_NATIVE_TEXTURE_VIEW_PLUGIN_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void NativeTextureViewPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_NATIVE_TEXTURE_VIEW_PLUGIN_H_ diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view.cc b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view.cc new file mode 100644 index 0000000..87ad752 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view.cc @@ -0,0 +1,82 @@ +#include "native_texture_view.h" + +namespace { + +ColorBarTexture::ColorBarTexture() : request_count_(0) { + constexpr size_t width = 1024; + constexpr size_t height = 640; + pixels_.reset(new uint8_t[width * height * 4]); + + buffer_ = std::make_unique(); + buffer_->buffer = pixels_.get(); + buffer_->width = width; + buffer_->height = height; +} + +const FlutterDesktopPixelBuffer *ColorBarTexture::CopyBuffer(size_t width, + size_t height) { + PrepareBuffer(); + request_count_++; + return buffer_.get(); +} + +void ColorBarTexture::PrepareBuffer() { + constexpr uint32_t kColorData[] = {0xFFFFFFFF, 0xFF00C0C0, 0xFFC0C000, + 0xFF00C000, 0xFFC000C0, 0xFF0000C0, + 0xFFC00000, 0xFF000000}; + auto data_num = sizeof(kColorData) / sizeof(uint32_t); + + auto *buffer = buffer_.get(); + auto pixel = const_cast( + reinterpret_cast(buffer->buffer)); + auto width = buffer->width; + auto height = buffer->height; + auto column_width = width / data_num; + auto offset = request_count_ % 8; + + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + auto index = (j / column_width) + offset; + index -= (index < data_num) ? 0 : data_num; + *(pixel++) = kColorData[index]; + } + } +} + +} // namespace + +NativeTextureView::NativeTextureView( + flutter::PluginRegistrar *registrar, int view_id, + flutter::TextureRegistrar *texture_registrar, double width, double height, + const std::vector ¶ms) + : FlutterDesktopPlatformView(registrar, view_id), + texture_registrar_(texture_registrar), width_(width), height_(height) { + color_bar_texture_ = std::make_unique(); + texture_ = + std::make_unique(flutter::PixelBufferTexture( + [this](size_t width, + size_t height) -> const FlutterDesktopPixelBuffer * { + return color_bar_texture_->CopyBuffer(width, height); + })); + SetTextureId(texture_registrar_->RegisterTexture(texture_.get())); +} + +NativeTextureView::~NativeTextureView() { Dispose(); } + +void NativeTextureView::Dispose() { + texture_registrar_->UnregisterTexture(GetTextureId()); +} + +void NativeTextureView::ClearFocus() {} + +void NativeTextureView::Resize(double width, double height) { + width_ = width; + height_ = height; +} + +void NativeTextureView::Touch(int device_id, int event_type, double x, + double y) { + texture_registrar_->MarkTextureFrameAvailable(GetTextureId()); +} + +void NativeTextureView::Offset(double top, double left) {} diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view.h b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view.h new file mode 100644 index 0000000..a72927c --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view.h @@ -0,0 +1,52 @@ +#ifndef FLUTTER_PLUGIN_NATIVE_TEXTURE_VIEW_H_ +#define FLUTTER_PLUGIN_NATIVE_TEXTURE_VIEW_H_ + +#include +#include +#include + +#include + +namespace { +class ColorBarTexture { +public: + ColorBarTexture(); + virtual ~ColorBarTexture() {} + const FlutterDesktopPixelBuffer *CopyBuffer(size_t width, size_t height); + +private: + void PrepareBuffer(); + + std::unique_ptr buffer_; + std::unique_ptr pixels_; + int32_t request_count_; +}; +} // namespace + +class NativeTextureView : public FlutterDesktopPlatformView { +public: + NativeTextureView(flutter::PluginRegistrar *registrar, int view_id, + flutter::TextureRegistrar *texture_registrar, double width, + double height, const std::vector ¶ms); + ~NativeTextureView(); + + virtual void Dispose() override; + + virtual void ClearFocus() override; + + virtual void Resize(double width, double height) override; + + virtual void Touch(int device_id, int event_type, double x, + double y) override; + + virtual void Offset(double top, double left) override; + +private: + flutter::TextureRegistrar *texture_registrar_; + std::unique_ptr texture_; + std::unique_ptr color_bar_texture_; + double width_; + double height_; +}; + +#endif // FLUTTER_PLUGIN_NATIVE_TEXTURE_VIEW_H_ diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view_factory.cc b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view_factory.cc new file mode 100644 index 0000000..eea0f1c --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view_factory.cc @@ -0,0 +1,18 @@ +#include "native_texture_view_factory.h" + +#include "native_texture_view.h" + +NativeTextureViewFactory::NativeTextureViewFactory( + flutter::PluginRegistrar *registrar) + : FlutterDesktopPlatformViewFactory(registrar) { + texture_registrar_ = registrar->texture_registrar(); +} + +FlutterDesktopPlatformView * +NativeTextureViewFactory::Create(int view_id, double width, double height, + const std::vector ¶ms) { + return new NativeTextureView(GetPluginRegistrar(), view_id, + texture_registrar_, width, height, params); +} + +void NativeTextureViewFactory::Dispose() {} diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view_factory.h b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view_factory.h new file mode 100644 index 0000000..42d9c57 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view_factory.h @@ -0,0 +1,24 @@ +#ifndef FLUTTER_PLUGIN_NATIVE_TEXTURE_VIEW_FACTORY_H_ +#define FLUTTER_PLUGIN_NATIVE_TEXTURE_VIEW_FACTORY_H_ + +#include +#include +#include + +#include + +class NativeTextureViewFactory : public FlutterDesktopPlatformViewFactory { +public: + NativeTextureViewFactory(flutter::PluginRegistrar *registrar); + + virtual FlutterDesktopPlatformView * + Create(int view_id, double width, double height, + const std::vector ¶ms) override; + + virtual void Dispose() override; + +private: + flutter::TextureRegistrar *texture_registrar_ = nullptr; +}; + +#endif // FLUTTER_PLUGIN_NATIVE_TEXTURE_VIEW_FACTORY_H_ diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view_plugin.cc b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view_plugin.cc new file mode 100644 index 0000000..fbf3fb2 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/elinux/native_texture_view_plugin.cc @@ -0,0 +1,33 @@ +#include "include/native_texture_view/native_texture_view_plugin.h" + +#include +#include +#include + +#include "native_texture_view_factory.h" + +namespace { +class NativeTextureViewPlugin : public flutter::Plugin { +public: + static void RegisterWithRegistrar(flutter::PluginRegistrar *registrar) { + auto plugin = std::make_unique(); + registrar->AddPlugin(std::move(plugin)); + } + + NativeTextureViewPlugin() {} + + virtual ~NativeTextureViewPlugin() {} +}; +} // namespace + +void NativeTextureViewPluginRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + flutter::PluginRegistrar *plugin_registrar = + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar); + + NativeTextureViewPlugin::RegisterWithRegistrar(plugin_registrar); + FlutterDesktopRegisterPlatformViewFactory( + registrar, "plugins.flutter.io/native_texture_view", + std::make_unique(plugin_registrar)); +} diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/pubspec.yaml b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/pubspec.yaml new file mode 100644 index 0000000..49207f6 --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/plugins/native_texture_view/pubspec.yaml @@ -0,0 +1,17 @@ +name: native_texture_view +description: Native texture view example plugin for flutter-elinux. +version: 0.1.0 + +environment: + sdk: ">=2.18.0 <4.0.0" + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter + +flutter: + plugin: + platforms: + elinux: + pluginClass: NativeTextureViewPlugin diff --git a/packages/flutter_elinux/examples/platform_views/native_texture_view/pubspec.yaml b/packages/flutter_elinux/examples/platform_views/native_texture_view/pubspec.yaml new file mode 100644 index 0000000..a1ed1be --- /dev/null +++ b/packages/flutter_elinux/examples/platform_views/native_texture_view/pubspec.yaml @@ -0,0 +1,24 @@ +name: native_texture_view_example +description: Native texture view example plugin for flutter-elinux. +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: '>=3.1.0 <4.0.0' + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.2 + flutter_elinux: + # You need to correctly replace the path below with the path of your flutter-elinux directory. + path: /home/hidenori/work/flutter/flutter-elinux/packages/flutter_elinux + native_texture_view: + path: plugins/native_texture_view + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 +flutter: + uses-material-design: true diff --git a/packages/flutter_elinux/lib/src/rendering/platform_view.dart b/packages/flutter_elinux/lib/src/rendering/platform_view.dart new file mode 100644 index 0000000..6217fe5 --- /dev/null +++ b/packages/flutter_elinux/lib/src/rendering/platform_view.dart @@ -0,0 +1,220 @@ +// Copyright 2023 Sony Group Corporation. All rights reserved. +// Copyright 2014 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:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; + +import '../services/platform_views.dart'; + +/// See: [_PlatformViewState] in `src/rendering/platform_view.dart` +enum _PlatformViewState { + uninitialized, + resizing, + ready, +} + +/// See: [RenderAndroidView] in `src/rendering/platform_view.dart` +class RenderELinuxView extends PlatformViewRenderBox { + /// Creates a render object for an ELinux view. + RenderELinuxView({ + required ELinuxViewController viewController, + required PlatformViewHitTestBehavior hitTestBehavior, + required Set> gestureRecognizers, + Clip clipBehavior = Clip.hardEdge, + }) : _viewController = viewController, + _clipBehavior = clipBehavior, + super( + controller: viewController, + hitTestBehavior: hitTestBehavior, + gestureRecognizers: gestureRecognizers) { + _viewController.pointTransformer = (Offset offset) => globalToLocal(offset); + updateGestureRecognizers(gestureRecognizers); + _viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated); + this.hitTestBehavior = hitTestBehavior; + _setOffset(); + } + + _PlatformViewState _state = _PlatformViewState.uninitialized; + + Size? _currentTextureSize; + + bool _isDisposed = false; + + /// The ELinux view controller for the ELinux view associated with this render object. + @override + ELinuxViewController get controller => _viewController; + + ELinuxViewController _viewController; + + /// Sets a new ELinux view controller. + @override + set controller(ELinuxViewController controller) { + assert(!_isDisposed); + if (_viewController == controller) { + return; + } + _viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated); + super.controller = controller; + _viewController = controller; + _viewController.pointTransformer = (Offset offset) => globalToLocal(offset); + _sizePlatformView(); + if (_viewController.isCreated) { + markNeedsSemanticsUpdate(); + } + _viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated); + } + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + + void _onPlatformViewCreated(int id) { + assert(!_isDisposed); + markNeedsSemanticsUpdate(); + } + + @override + bool get sizedByParent => true; + + @override + bool get alwaysNeedsCompositing => true; + + @override + bool get isRepaintBoundary => true; + + @override + Size computeDryLayout(BoxConstraints constraints) { + return constraints.biggest; + } + + @override + void performResize() { + super.performResize(); + _sizePlatformView(); + } + + Future _sizePlatformView() async { + // ELinux virtual displays cannot have a zero size. + // Trying to size it to 0 crashes the app, which was happening when starting the app + // with a locked screen (see: https://github.com/flutter/flutter/issues/20456). + if (_state == _PlatformViewState.resizing || size.isEmpty) { + return; + } + + _state = _PlatformViewState.resizing; + markNeedsPaint(); + + Size targetSize; + do { + targetSize = size; + _currentTextureSize = await _viewController.setSize(targetSize); + if (_isDisposed) { + return; + } + // We've resized the platform view to targetSize, but it is possible that + // while we were resizing the render object's size was changed again. + // In that case we will resize the platform view again. + } while (size != targetSize); + + _state = _PlatformViewState.ready; + markNeedsPaint(); + } + + // Sets the offset of the underlying platform view on the platform side. + // + // This allows the ELinux native view to draw the a11y highlights in the same + // location on the screen as the platform view widget in the Flutter framework. + // + // It also allows platform code to obtain the correct position of the ELinux + // native view on the screen. + void _setOffset() { + SchedulerBinding.instance.addPostFrameCallback((_) async { + if (!_isDisposed) { + if (attached) { + await _viewController.setOffset(localToGlobal(Offset.zero)); + } + // Schedule a new post frame callback. + _setOffset(); + } + }); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (_viewController.textureId == null || _currentTextureSize == null) { + return; + } + + // As resizing the ELinux view happens asynchronously we don't know exactly when is a + // texture frame with the new size is ready for consumption. + // TextureLayer is unaware of the texture frame's size and always maps it to the + // specified rect. If the rect we provide has a different size from the current texture frame's + // size the texture frame will be scaled. + // To prevent unwanted scaling artifacts while resizing, clip the texture. + // This guarantees that the size of the texture frame we're painting is always + // _currentELinuxTextureSize. + final bool isTextureLargerThanWidget = + _currentTextureSize!.width > size.width || + _currentTextureSize!.height > size.height; + if (isTextureLargerThanWidget && clipBehavior != Clip.none) { + _clipRectLayer.layer = context.pushClipRect( + true, + offset, + offset & size, + _paintTexture, + clipBehavior: clipBehavior, + oldLayer: _clipRectLayer.layer, + ); + return; + } + _clipRectLayer.layer = null; + _paintTexture(context, offset); + } + + final LayerHandle _clipRectLayer = + LayerHandle(); + + @override + void dispose() { + _isDisposed = true; + _clipRectLayer.layer = null; + _viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated); + super.dispose(); + } + + void _paintTexture(PaintingContext context, Offset offset) { + if (_currentTextureSize == null) { + return; + } + + context.addLayer(TextureLayer( + rect: offset & _currentTextureSize!, + textureId: _viewController.textureId!, + )); + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + // Don't call the super implementation since `platformViewId` should + // be set only when the platform view is created, but the concept of + // a "created" platform view belongs to this subclass. + config.isSemanticBoundary = true; + + if (_viewController.isCreated) { + config.platformViewId = _viewController.viewId; + } + } +} diff --git a/packages/flutter_elinux/lib/src/services/platform_views.dart b/packages/flutter_elinux/lib/src/services/platform_views.dart new file mode 100644 index 0000000..d6c1e92 --- /dev/null +++ b/packages/flutter_elinux/lib/src/services/platform_views.dart @@ -0,0 +1,1238 @@ +// Copyright 2023 Sony Group Corporation. All rights reserved. +// Copyright 2014 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 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; + +export 'dart:ui' show Offset, Size, TextDirection, VoidCallback; + +export 'package:flutter/gestures.dart' show PointerEvent; + +/// See: [PlatformViewsService] in `src/services/platform_view.dart` +class PlatformViewsServiceELinux { + PlatformViewsServiceELinux._() { + SystemChannels.platform_views.setMethodCallHandler(_onMethodCall); + } + + static final PlatformViewsServiceELinux _instance = + PlatformViewsServiceELinux._(); + + Future _onMethodCall(MethodCall call) { + switch (call.method) { + case 'viewFocused': + final int id = call.arguments as int; + if (_focusCallbacks.containsKey(id)) { + _focusCallbacks[id]!(); + } + break; + default: + throw UnimplementedError( + "${call.method} was invoked but isn't implemented by PlatformViewsService"); + } + return Future.value(); + } + + /// Maps platform view IDs to focus callbacks. + /// + /// The callbacks are invoked when the platform view asks to be focused. + final Map _focusCallbacks = {}; + + /// {@template flutter.services.PlatformViewsService.initELinuxView} + /// Creates a controller for a new ELinux view. + /// + /// `id` is an unused unique identifier generated with [platformViewsRegistry]. + /// + /// `viewType` is the identifier of the ELinux view type to be created, a + /// factory for this view type must have been registered on the platform side. + /// Platform view factories are typically registered by plugin code. + /// Plugins can register a platform view factory with + /// [PlatformViewRegistry#registerViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html#registerViewFactory-java.lang.String-io.flutter.plugin.platform.PlatformViewFactory-). + /// + /// `creationParams` will be passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-ELinux.content.Context-int-java.lang.Object-) + /// + /// `creationParamsCodec` is the codec used to encode `creationParams` before sending it to the + /// platform side. It should match the codec passed to the constructor of [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-). + /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]. + /// + /// `onFocus` is a callback that will be invoked when the ELinux View asks to get the + /// input focus. + /// + /// The ELinux view will only be created after [ELinuxViewController.setSize] is called for the + /// first time. + /// + /// The `id, `viewType, and `layoutDirection` parameters must not be null. + /// If `creationParams` is non null then `creationParamsCodec` must not be null. + /// {@endtemplate} + /// + /// This attempts to use the newest and most efficient platform view + /// implementation when possible. In cases where that is not supported, it + /// falls back to using Virtual Display. + static ELinuxViewController initELinuxView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + assert(creationParams == null || creationParamsCodec != null); + + final TextureELinuxViewController controller = + TextureELinuxViewController._( + viewId: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + ); + + _instance._focusCallbacks[id] = onFocus ?? () {}; + return controller; + } + + /// {@macro flutter.services.PlatformViewsService.initELinuxView} + /// + /// This attempts to use the newest and most efficient platform view + /// implementation when possible. In cases where that is not supported, it + /// falls back to using Hybrid Composition, which is the mode used by + /// [initExpensiveELinuxView]. + static SurfaceELinuxViewController initSurfaceELinuxView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + assert(creationParams == null || creationParamsCodec != null); + + final SurfaceELinuxViewController controller = + SurfaceELinuxViewController._( + viewId: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + ); + _instance._focusCallbacks[id] = onFocus ?? () {}; + return controller; + } + + /// {@macro flutter.services.PlatformViewsService.initELinuxView} + /// + /// When this factory is used, the ELinux view and Flutter widgets are + /// composed at the ELinux view hierarchy level. + /// + /// Using this method has a performance cost on devices running ELinux 9 or + /// earlier, or on underpowered devices. In most situations, you should use + /// [initELinuxView] or [initSurfaceELinuxView] instead. + static ExpensiveELinuxViewController initExpensiveELinuxView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + final ExpensiveELinuxViewController controller = + ExpensiveELinuxViewController._( + viewId: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + ); + + _instance._focusCallbacks[id] = onFocus ?? () {}; + return controller; + } + + /// Whether the render surface of the ELinux `FlutterView` should be converted to a `FlutterImageView`. + @Deprecated( + 'No longer necessary to improve performance. ' + 'This feature was deprecated after v2.11.0-0.1.pre.', + ) + static Future synchronizeToNativeViewHierarchy(bool yes) async {} +} + +/// See: [AndroidPointerProperties] in `src/services/platform_view.dart` +class ELinuxPointerProperties { + /// Creates an [ELinuxPointerProperties] object. + /// + /// All parameters must not be null. + const ELinuxPointerProperties({ + required this.id, + required this.toolType, + }); + + /// See ELinux's [MotionEvent.PointerProperties#id](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties.html#id). + final int id; + + /// The type of tool used to make contact such as a finger or stylus, if known. + /// See ELinux's [MotionEvent.PointerProperties#toolType](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties.html#toolType). + final int toolType; + + /// Value for `toolType` when the tool type is unknown. + static const int kToolTypeUnknown = 0; + + /// Value for `toolType` when the tool type is a finger. + static const int kToolTypeFinger = 1; + + /// Value for `toolType` when the tool type is a stylus. + static const int kToolTypeStylus = 2; + + /// Value for `toolType` when the tool type is a mouse. + static const int kToolTypeMouse = 3; + + /// Value for `toolType` when the tool type is an eraser. + static const int kToolTypeEraser = 4; + + List _asList() => [id, toolType]; + + @override + String toString() { + return '${objectRuntimeType(this, 'ELinuxPointerProperties')}(id: $id, toolType: $toolType)'; + } +} + +/// See: [AndroidPointerCoords] in `src/services/platform_view.dart` +class ELinuxPointerCoords { + /// Creates an ELinuxPointerCoords. + /// + /// All parameters must not be null. + const ELinuxPointerCoords({ + required this.orientation, + required this.pressure, + required this.size, + required this.toolMajor, + required this.toolMinor, + required this.touchMajor, + required this.touchMinor, + required this.x, + required this.y, + }); + + /// The orientation of the touch area and tool area in radians clockwise from vertical. + /// + /// See ELinux's [MotionEvent.PointerCoords#orientation](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#orientation). + final double orientation; + + /// A normalized value that describes the pressure applied to the device by a finger or other tool. + /// + /// See ELinux's [MotionEvent.PointerCoords#pressure](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#pressure). + final double pressure; + + /// A normalized value that describes the approximate size of the pointer touch area in relation to the maximum detectable size of the device. + /// + /// See ELinux's [MotionEvent.PointerCoords#size](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#size). + final double size; + + /// See ELinux's [MotionEvent.PointerCoords#toolMajor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#toolMajor). + final double toolMajor; + + /// See ELinux's [MotionEvent.PointerCoords#toolMinor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#toolMinor). + final double toolMinor; + + /// See ELinux's [MotionEvent.PointerCoords#touchMajor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#touchMajor). + final double touchMajor; + + /// See ELinux's [MotionEvent.PointerCoords#touchMinor](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#touchMinor). + final double touchMinor; + + /// The X component of the pointer movement. + /// + /// See ELinux's [MotionEvent.PointerCoords#x](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#x). + final double x; + + /// The Y component of the pointer movement. + /// + /// See ELinux's [MotionEvent.PointerCoords#y](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords.html#y). + final double y; + + List _asList() { + return [ + orientation, + pressure, + size, + toolMajor, + toolMinor, + touchMajor, + touchMinor, + x, + y, + ]; + } + + @override + String toString() { + return '${objectRuntimeType(this, 'ELinuxPointerCoords')}(orientation: $orientation, pressure: $pressure, size: $size, toolMajor: $toolMajor, toolMinor: $toolMinor, touchMajor: $touchMajor, touchMinor: $touchMinor, x: $x, y: $y)'; + } +} + +/// See: [AndroidMotionEvent] in `src/services/platform_view.dart` +class ELinuxMotionEvent { + /// Creates an ELinuxMotionEvent. + /// + /// All parameters must not be null. + ELinuxMotionEvent({ + required this.downTime, + required this.eventTime, + required this.action, + required this.pointerCount, + required this.pointerProperties, + required this.pointerCoords, + required this.metaState, + required this.buttonState, + required this.xPrecision, + required this.yPrecision, + required this.deviceId, + required this.edgeFlags, + required this.source, + required this.flags, + required this.motionEventId, + }) : assert(pointerProperties.length == pointerCount), + assert(pointerCoords.length == pointerCount); + + /// The time (in ms) when the user originally pressed down to start a stream of position events, + /// relative to an arbitrary timeline. + /// + /// See ELinux's [MotionEvent#getDownTime](https://developer.android.com/reference/android/view/MotionEvent.html#getDownTime()). + final int downTime; + + /// The time this event occurred, relative to an arbitrary timeline. + /// + /// See ELinux's [MotionEvent#getEventTime](https://developer.android.com/reference/android/view/MotionEvent.html#getEventTime()). + final int eventTime; + + /// A value representing the kind of action being performed. + /// + /// See ELinux's [MotionEvent#getAction](https://developer.android.com/reference/android/view/MotionEvent.html#getAction()). + final int action; + + /// The number of pointers that are part of this event. + /// This must be equivalent to the length of `pointerProperties` and `pointerCoords`. + /// + /// See ELinux's [MotionEvent#getPointerCount](https://developer.android.com/reference/android/view/MotionEvent.html#getPointerCount()). + final int pointerCount; + + /// List of [ELinuxPointerProperties] for each pointer that is part of this event. + final List pointerProperties; + + /// List of [ELinuxPointerCoords] for each pointer that is part of this event. + final List pointerCoords; + + /// The state of any meta / modifier keys that were in effect when the event was generated. + /// + /// See ELinux's [MotionEvent#getMetaState](https://developer.android.com/reference/android/view/MotionEvent.html#getMetaState()). + final int metaState; + + /// The state of all buttons that are pressed such as a mouse or stylus button. + /// + /// See ELinux's [MotionEvent#getButtonState](https://developer.android.com/reference/android/view/MotionEvent.html#getButtonState()). + final int buttonState; + + /// The precision of the X coordinates being reported, in physical pixels. + /// + /// See ELinux's [MotionEvent#getXPrecision](https://developer.android.com/reference/android/view/MotionEvent.html#getXPrecision()). + final double xPrecision; + + /// The precision of the Y coordinates being reported, in physical pixels. + /// + /// See ELinux's [MotionEvent#getYPrecision](https://developer.android.com/reference/android/view/MotionEvent.html#getYPrecision()). + final double yPrecision; + + /// See ELinux's [MotionEvent#getDeviceId](https://developer.android.com/reference/android/view/MotionEvent.html#getDeviceId()). + final int deviceId; + + /// A bit field indicating which edges, if any, were touched by this MotionEvent. + /// + /// See ELinux's [MotionEvent#getEdgeFlags](https://developer.android.com/reference/android/view/MotionEvent.html#getEdgeFlags()). + final int edgeFlags; + + /// The source of this event (e.g a touchpad or stylus). + /// + /// See ELinux's [MotionEvent#getSource](https://developer.android.com/reference/android/view/MotionEvent.html#getSource()). + final int source; + + /// See ELinux's [MotionEvent#getFlags](https://developer.android.com/reference/android/view/MotionEvent.html#getFlags()). + final int flags; + + /// Used to identify this [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent.html) uniquely in the Flutter Engine. + final int motionEventId; + + List _asList(int viewId) { + return [ + viewId, + downTime, + eventTime, + action, + pointerCount, + pointerProperties + .map>((ELinuxPointerProperties p) => p._asList()) + .toList(), + pointerCoords + .map>((ELinuxPointerCoords p) => p._asList()) + .toList(), + metaState, + buttonState, + xPrecision, + yPrecision, + deviceId, + edgeFlags, + source, + flags, + motionEventId, + ]; + } + + @override + String toString() { + return 'ELinuxPointerEvent(downTime: $downTime, eventTime: $eventTime, action: $action, pointerCount: $pointerCount, pointerProperties: $pointerProperties, pointerCoords: $pointerCoords, metaState: $metaState, buttonState: $buttonState, xPrecision: $xPrecision, yPrecision: $yPrecision, deviceId: $deviceId, edgeFlags: $edgeFlags, source: $source, flags: $flags, motionEventId: $motionEventId)'; + } +} + +/// See: [_AndroidViewState] in `src/services/platform_view.dart` +enum _ELinuxViewState { + waitingForSize, + creating, + created, + disposed, +} + +/// See: [_AndroidMotionEventConverter] in `src/services/platform_view.dart` +class _ELinuxMotionEventConverter { + _ELinuxMotionEventConverter(); + + final Map pointerPositions = + {}; + final Map pointerProperties = + {}; + final Set usedELinuxPointerIds = {}; + + late PointTransformer pointTransformer; + + int? downTimeMillis; + + void handlePointerDownEvent(PointerDownEvent event) { + if (pointerProperties.isEmpty) { + downTimeMillis = event.timeStamp.inMilliseconds; + } + int ELinuxPointerId = 0; + while (usedELinuxPointerIds.contains(ELinuxPointerId)) { + ELinuxPointerId++; + } + usedELinuxPointerIds.add(ELinuxPointerId); + pointerProperties[event.pointer] = propertiesFor(event, ELinuxPointerId); + } + + void updatePointerPositions(PointerEvent event) { + final Offset position = pointTransformer(event.position); + pointerPositions[event.pointer] = ELinuxPointerCoords( + orientation: event.orientation, + pressure: event.pressure, + size: event.size, + toolMajor: event.radiusMajor, + toolMinor: event.radiusMinor, + touchMajor: event.radiusMajor, + touchMinor: event.radiusMinor, + x: position.dx, + y: position.dy, + ); + } + + void _remove(int pointer) { + pointerPositions.remove(pointer); + usedELinuxPointerIds.remove(pointerProperties[pointer]!.id); + pointerProperties.remove(pointer); + if (pointerProperties.isEmpty) { + downTimeMillis = null; + } + } + + void handlePointerUpEvent(PointerUpEvent event) { + _remove(event.pointer); + } + + void handlePointerCancelEvent(PointerCancelEvent event) { + // The pointer cancel event is handled like pointer up. Normally, + // the difference is that pointer cancel doesn't perform any action, + // but in this case neither up or cancel perform any action. + _remove(event.pointer); + } + + ELinuxMotionEvent? toELinuxMotionEvent(PointerEvent event) { + final List pointers = pointerPositions.keys.toList(); + final int pointerIdx = pointers.indexOf(event.pointer); + final int numPointers = pointers.length; + + // This value must match the value in engine's FlutterView.java. + // This flag indicates whether the original ELinux pointer events were batched together. + const int kPointerDataFlagBatched = 1; + + // ELinux MotionEvent objects can batch information on multiple pointers. + // Flutter breaks these such batched events into multiple PointerEvent objects. + // When there are multiple active pointers we accumulate the information for all pointers + // as we get PointerEvents, and only send it to the embedded ELinux view when + // we see the last pointer. This way we achieve the same batching as ELinux. + if (event.platformData == kPointerDataFlagBatched || + (isSinglePointerAction(event) && pointerIdx < numPointers - 1)) { + return null; + } + + final int action; + if (event is PointerDownEvent) { + action = numPointers == 1 + ? ELinuxViewController.kActionDown + : ELinuxViewController.pointerAction( + pointerIdx, ELinuxViewController.kActionPointerDown); + } else if (event is PointerUpEvent) { + action = numPointers == 1 + ? ELinuxViewController.kActionUp + : ELinuxViewController.pointerAction( + pointerIdx, ELinuxViewController.kActionPointerUp); + } else if (event is PointerMoveEvent) { + action = ELinuxViewController.kActionMove; + } else if (event is PointerCancelEvent) { + action = ELinuxViewController.kActionCancel; + } else { + return null; + } + + return ELinuxMotionEvent( + downTime: downTimeMillis!, + eventTime: event.timeStamp.inMilliseconds, + action: action, + pointerCount: pointerPositions.length, + pointerProperties: pointers + .map((int i) => pointerProperties[i]!) + .toList(), + pointerCoords: pointers + .map((int i) => pointerPositions[i]!) + .toList(), + metaState: 0, + buttonState: 0, + xPrecision: 1.0, + yPrecision: 1.0, + deviceId: 0, + edgeFlags: 0, + source: 0, + flags: 0, + motionEventId: event.embedderId, + ); + } + + ELinuxPointerProperties propertiesFor(PointerEvent event, int pointerId) { + int toolType = ELinuxPointerProperties.kToolTypeUnknown; + switch (event.kind) { + case PointerDeviceKind.touch: + case PointerDeviceKind.trackpad: + toolType = ELinuxPointerProperties.kToolTypeFinger; + break; + case PointerDeviceKind.mouse: + toolType = ELinuxPointerProperties.kToolTypeMouse; + break; + case PointerDeviceKind.stylus: + toolType = ELinuxPointerProperties.kToolTypeStylus; + break; + case PointerDeviceKind.invertedStylus: + toolType = ELinuxPointerProperties.kToolTypeEraser; + break; + case PointerDeviceKind.unknown: + toolType = ELinuxPointerProperties.kToolTypeUnknown; + break; + } + return ELinuxPointerProperties(id: pointerId, toolType: toolType); + } + + bool isSinglePointerAction(PointerEvent event) => + event is! PointerDownEvent && event is! PointerUpEvent; +} + +/// See: [_CreationParams] in `src/services/platform_view.dart` +class _CreationParams { + const _CreationParams(this.data, this.codec); + final dynamic data; + final MessageCodec codec; +} + +/// See: [AndroidViewController] in `src/services/platform_view.dart` +abstract class ELinuxViewController extends PlatformViewController { + ELinuxViewController._({ + required this.viewId, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + }) : assert(creationParams == null || creationParamsCodec != null), + _viewType = viewType, + _layoutDirection = layoutDirection, + _creationParams = creationParams == null + ? null + : _CreationParams(creationParams, creationParamsCodec!); + + /// Action code for when a primary pointer touched the screen. + /// + /// ELinux's [MotionEvent.ACTION_DOWN](https://developer.android.com/reference/android/view/MotionEvent#ACTION_DOWN) + static const int kActionDown = 0; + + /// Action code for when a primary pointer stopped touching the screen. + /// + /// ELinux's [MotionEvent.ACTION_UP](https://developer.android.com/reference/android/view/MotionEvent#ACTION_UP) + static const int kActionUp = 1; + + /// Action code for when the event only includes information about pointer movement. + /// + /// ELinux's [MotionEvent.ACTION_MOVE](https://developer.android.com/reference/android/view/MotionEvent#ACTION_MOVE) + static const int kActionMove = 2; + + /// Action code for when a motion event has been canceled. + /// + /// ELinux's [MotionEvent.ACTION_CANCEL](https://developer.android.com/reference/android/view/MotionEvent#ACTION_CANCEL) + static const int kActionCancel = 3; + + /// Action code for when a secondary pointer touched the screen. + /// + /// ELinux's [MotionEvent.ACTION_POINTER_DOWN](https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_DOWN) + static const int kActionPointerDown = 5; + + /// Action code for when a secondary pointer stopped touching the screen. + /// + /// ELinux's [MotionEvent.ACTION_POINTER_UP](https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_UP) + static const int kActionPointerUp = 6; + + /// ELinux's [View.LAYOUT_DIRECTION_LTR](https://developer.android.com/reference/android/view/View.html#LAYOUT_DIRECTION_LTR) value. + static const int kELinuxLayoutDirectionLtr = 0; + + /// ELinux's [View.LAYOUT_DIRECTION_RTL](https://developer.android.com/reference/android/view/View.html#LAYOUT_DIRECTION_RTL) value. + static const int kELinuxLayoutDirectionRtl = 1; + + /// The unique identifier of the ELinux view controlled by this controller. + @override + final int viewId; + + final String _viewType; + + // Helps convert PointerEvents to ELinuxMotionEvents. + final _ELinuxMotionEventConverter _motionEventConverter = + _ELinuxMotionEventConverter(); + + TextDirection _layoutDirection; + + _ELinuxViewState _state = _ELinuxViewState.waitingForSize; + + final _CreationParams? _creationParams; + + final List _platformViewCreatedCallbacks = + []; + + static int _getELinuxDirection(TextDirection direction) { + switch (direction) { + case TextDirection.ltr: + return kELinuxLayoutDirectionLtr; + case TextDirection.rtl: + return kELinuxLayoutDirectionRtl; + } + } + + /// Creates a masked ELinux MotionEvent action value for an indexed pointer. + static int pointerAction(int pointerId, int action) { + return ((pointerId << 8) & 0xff00) | (action & 0xff); + } + + /// Sends the message to dispose the platform view. + Future _sendDisposeMessage(); + + /// True if [_sendCreateMessage] can only be called with a non-null size. + bool get _createRequiresSize; + + /// Sends the message to create the platform view with an initial [size]. + /// + /// If [_createRequiresSize] is true, `size` is non-nullable, and the call + /// should instead be deferred until the size is available. + Future _sendCreateMessage( + {required covariant Size? size, Offset? position}); + + /// Sends the message to resize the platform view to [size]. + Future _sendResizeMessage(Size size); + + @override + bool get awaitingCreation => _state == _ELinuxViewState.waitingForSize; + + @override + Future create({Size? size, Offset? position}) async { + assert(_state != _ELinuxViewState.disposed, + 'trying to create a disposed ELinux view'); + assert(_state == _ELinuxViewState.waitingForSize, + 'ELinux view is already sized. View id: $viewId'); + + if (_createRequiresSize && size == null) { + // Wait for a setSize call. + return; + } + + _state = _ELinuxViewState.creating; + await _sendCreateMessage(size: size, position: position); + _state = _ELinuxViewState.created; + + for (final PlatformViewCreatedCallback callback + in _platformViewCreatedCallbacks) { + callback(viewId); + } + } + + /// Sizes the ELinux View. + /// + /// [size] is the view's new size in logical pixel, it must not be null and must + /// be bigger than zero. + /// + /// The first time a size is set triggers the creation of the ELinux view. + /// + /// Returns the buffer size in logical pixel that backs the texture where the platform + /// view pixels are written to. + /// + /// The buffer size may or may not be the same as [size]. + /// + /// As a result, consumers are expected to clip the texture using [size], while using + /// the return value to size the texture. + Future setSize(Size size) async { + assert(_state != _ELinuxViewState.disposed, + 'ELinux view is disposed. View id: $viewId'); + if (_state == _ELinuxViewState.waitingForSize) { + // Either `create` hasn't been called, or it couldn't run due to missing + // size information, so create the view now. + await create(size: size); + return size; + } else { + return _sendResizeMessage(size); + } + } + + /// Sets the offset of the platform view. + /// + /// [off] is the view's new offset in logical pixel. + /// + /// On ELinux, this allows the ELinux native view to draw the a11y highlights in the same + /// location on the screen as the platform view widget in the Flutter framework. + Future setOffset(Offset off); + + /// Returns the texture entry id that the ELinux view is rendering into. + /// + /// Returns null if the ELinux view has not been successfully created, if it has been + /// disposed, or if the implementation does not use textures. + int? get textureId; + + /// True if the view requires native view composition rather than using a + /// texture to render. + /// + /// This value may change during [create], but will not change after that + /// call's future has completed. + bool get requiresViewComposition => false; + + /// Sends an ELinux [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent) + /// to the view. + /// + /// The ELinux MotionEvent object is created with [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)). + /// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)) + /// for description of the parameters. + /// + /// See [ELinuxViewController.dispatchPointerEvent] for sending a + /// [PointerEvent]. + Future sendMotionEvent(ELinuxMotionEvent event) async { + await SystemChannels.platform_views.invokeMethod( + 'touch', + event._asList(viewId), + ); + } + + /// Converts a given point from the global coordinate system in logical pixels + /// to the local coordinate system for this box. + /// + /// This is required to convert a [PointerEvent] to an [ELinuxMotionEvent]. + /// It is typically provided by using [RenderBox.globalToLocal]. + PointTransformer get pointTransformer => + _motionEventConverter.pointTransformer; + set pointTransformer(PointTransformer transformer) { + _motionEventConverter.pointTransformer = transformer; + } + + /// Whether the platform view has already been created. + bool get isCreated => _state == _ELinuxViewState.created; + + /// Adds a callback that will get invoke after the platform view has been + /// created. + void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) { + assert(_state != _ELinuxViewState.disposed); + _platformViewCreatedCallbacks.add(listener); + } + + /// Removes a callback added with [addOnPlatformViewCreatedListener]. + void removeOnPlatformViewCreatedListener( + PlatformViewCreatedCallback listener) { + assert(_state != _ELinuxViewState.disposed); + _platformViewCreatedCallbacks.remove(listener); + } + + /// The created callbacks that are invoked after the platform view has been + /// created. + @visibleForTesting + List get createdCallbacks => + _platformViewCreatedCallbacks; + + /// Sets the layout direction for the ELinux view. + Future setLayoutDirection(TextDirection layoutDirection) async { + assert( + _state != _ELinuxViewState.disposed, + 'trying to set a layout direction for a disposed ELinux view. View id: $viewId', + ); + + if (layoutDirection == _layoutDirection) { + return; + } + + _layoutDirection = layoutDirection; + + // If the view was not yet created we just update _layoutDirection and return, as the new + // direction will be used in _create. + if (_state == _ELinuxViewState.waitingForSize) { + return; + } + + await SystemChannels.platform_views + .invokeMethod('setDirection', { + 'id': viewId, + 'direction': _getELinuxDirection(layoutDirection), + }); + } + + /// Converts the [PointerEvent] and sends an ELinux [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent) + /// to the view. + /// + /// This method can only be used if a [PointTransformer] is provided to + /// [ELinuxViewController.pointTransformer]. Otherwise, an [AssertionError] + /// is thrown. See [ELinuxViewController.sendMotionEvent] for sending a + /// `MotionEvent` without a [PointTransformer]. + /// + /// The ELinux MotionEvent object is created with [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)). + /// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)) + /// for description of the parameters. + @override + Future dispatchPointerEvent(PointerEvent event) async { + if (event is PointerHoverEvent) { + return; + } + + if (event is PointerDownEvent) { + _motionEventConverter.handlePointerDownEvent(event); + } + + _motionEventConverter.updatePointerPositions(event); + + final ELinuxMotionEvent? ELinuxEvent = + _motionEventConverter.toELinuxMotionEvent(event); + + if (event is PointerUpEvent) { + _motionEventConverter.handlePointerUpEvent(event); + } else if (event is PointerCancelEvent) { + _motionEventConverter.handlePointerCancelEvent(event); + } + + if (ELinuxEvent != null) { + await sendMotionEvent(ELinuxEvent); + } + } + + /// Clears the focus from the ELinux View if it is focused. + @override + Future clearFocus() { + if (_state != _ELinuxViewState.created) { + return Future.value(); + } + return SystemChannels.platform_views + .invokeMethod('clearFocus', viewId); + } + + /// Disposes the ELinux view. + /// + /// The [ELinuxViewController] object is unusable after calling this. + /// The identifier of the platform view cannot be reused after the view is + /// disposed. + @override + Future dispose() async { + final _ELinuxViewState state = _state; + _state = _ELinuxViewState.disposed; + _platformViewCreatedCallbacks.clear(); + PlatformViewsServiceELinux._instance._focusCallbacks.remove(viewId); + if (state == _ELinuxViewState.creating || + state == _ELinuxViewState.created) { + await _sendDisposeMessage(); + } + } +} + +/// See: [SurfaceAndroidViewController] in `src/services/platform_view.dart` +class SurfaceELinuxViewController extends ELinuxViewController { + SurfaceELinuxViewController._({ + required super.viewId, + required super.viewType, + required super.layoutDirection, + super.creationParams, + super.creationParamsCodec, + }) : super._(); + + // By default, assume the implementation will be texture-based. + _ELinuxViewControllerInternals _internals = + _TextureELinuxViewControllerInternals(); + + @override + bool get _createRequiresSize => true; + + @override + Future _sendCreateMessage( + {required Size size, Offset? position}) async { + assert(!size.isEmpty, + 'trying to create $TextureELinuxViewController without setting a valid size.'); + + final dynamic response = + await _ELinuxViewControllerInternals.sendCreateMessage( + viewId: viewId, + viewType: _viewType, + hybrid: false, + hybridFallback: true, + layoutDirection: _layoutDirection, + creationParams: _creationParams, + size: size, + position: position, + ); + if (response is int) { + (_internals as _TextureELinuxViewControllerInternals).textureId = + response; + } else { + // A null response indicates fallback to Hybrid Composition, so swap out + // the implementation. + _internals = _HybridELinuxViewControllerInternals(); + } + return true; + } + + @override + int? get textureId { + return _internals.textureId; + } + + @override + bool get requiresViewComposition { + return _internals.requiresViewComposition; + } + + @override + Future _sendDisposeMessage() { + return _internals.sendDisposeMessage(viewId: viewId); + } + + @override + Future _sendResizeMessage(Size size) { + return _internals.setSize(size, viewId: viewId, viewState: _state); + } + + @override + Future setOffset(Offset off) { + return _internals.setOffset(off, viewId: viewId, viewState: _state); + } +} + +/// See: [ExpensiveAndroidViewController] in `src/services/platform_view.dart` +class ExpensiveELinuxViewController extends ELinuxViewController { + ExpensiveELinuxViewController._({ + required super.viewId, + required super.viewType, + required super.layoutDirection, + super.creationParams, + super.creationParamsCodec, + }) : super._(); + + final _ELinuxViewControllerInternals _internals = + _HybridELinuxViewControllerInternals(); + + @override + bool get _createRequiresSize => false; + + @override + Future _sendCreateMessage( + {required Size? size, Offset? position}) async { + await _ELinuxViewControllerInternals.sendCreateMessage( + viewId: viewId, + viewType: _viewType, + hybrid: true, + layoutDirection: _layoutDirection, + creationParams: _creationParams, + position: position, + ); + } + + @override + int? get textureId { + return _internals.textureId; + } + + @override + bool get requiresViewComposition { + return _internals.requiresViewComposition; + } + + @override + Future _sendDisposeMessage() { + return _internals.sendDisposeMessage(viewId: viewId); + } + + @override + Future _sendResizeMessage(Size size) { + return _internals.setSize(size, viewId: viewId, viewState: _state); + } + + @override + Future setOffset(Offset off) { + return _internals.setOffset(off, viewId: viewId, viewState: _state); + } +} + +/// See: [TextureAndroidViewController] in `src/services/platform_view.dart` +class TextureELinuxViewController extends ELinuxViewController { + TextureELinuxViewController._({ + required super.viewId, + required super.viewType, + required super.layoutDirection, + super.creationParams, + super.creationParamsCodec, + }) : super._(); + + final _TextureELinuxViewControllerInternals _internals = + _TextureELinuxViewControllerInternals(); + + @override + bool get _createRequiresSize => true; + + @override + Future _sendCreateMessage( + {required Size size, Offset? position}) async { + assert(!size.isEmpty, + 'trying to create $TextureELinuxViewController without setting a valid size.'); + + _internals.textureId = + await _ELinuxViewControllerInternals.sendCreateMessage( + viewId: viewId, + viewType: _viewType, + hybrid: false, + layoutDirection: _layoutDirection, + creationParams: _creationParams, + size: size, + position: position, + ) as int; + } + + @override + int? get textureId { + return _internals.textureId; + } + + @override + bool get requiresViewComposition { + return _internals.requiresViewComposition; + } + + @override + Future _sendDisposeMessage() { + return _internals.sendDisposeMessage(viewId: viewId); + } + + @override + Future _sendResizeMessage(Size size) { + return _internals.setSize(size, viewId: viewId, viewState: _state); + } + + @override + Future setOffset(Offset off) { + return _internals.setOffset(off, viewId: viewId, viewState: _state); + } +} + +/// See: [_AndroidViewControllerInternals] in `src/services/platform_view.dart` +abstract class _ELinuxViewControllerInternals { + // Sends a create message with the given parameters, and returns the result + // if any. + // + // This uses a dynamic return because depending on the mode that is selected + // on the native side, the return type is different. Callers should cast + // depending on the possible return types for their arguments. + static Future sendCreateMessage( + {required int viewId, + required String viewType, + required TextDirection layoutDirection, + required bool hybrid, + bool hybridFallback = false, + _CreationParams? creationParams, + Size? size, + Offset? position}) { + final Map args = { + 'id': viewId, + 'viewType': viewType, + 'direction': ELinuxViewController._getELinuxDirection(layoutDirection), + if (hybrid) 'hybrid': hybrid, + if (size != null) 'width': size.width, + if (size != null) 'height': size.height, + if (hybridFallback) 'hybridFallback': hybridFallback, + if (position != null) 'left': position.dx, + if (position != null) 'top': position.dy, + }; + if (creationParams != null) { + final ByteData paramsByteData = + creationParams.codec.encodeMessage(creationParams.data)!; + args['params'] = Uint8List.view( + paramsByteData.buffer, + 0, + paramsByteData.lengthInBytes, + ); + } + return SystemChannels.platform_views.invokeMethod('create', args); + } + + int? get textureId; + + bool get requiresViewComposition; + + Future setSize( + Size size, { + required int viewId, + required _ELinuxViewState viewState, + }); + + Future setOffset( + Offset offset, { + required int viewId, + required _ELinuxViewState viewState, + }); + + Future sendDisposeMessage({required int viewId}); +} + +/// See: [_TextureAndroidViewControllerInternals] in `src/services/platform_view.dart` +class _TextureELinuxViewControllerInternals + extends _ELinuxViewControllerInternals { + _TextureELinuxViewControllerInternals(); + + /// The current offset of the platform view. + Offset _offset = Offset.zero; + + @override + int? textureId; + + @override + bool get requiresViewComposition => false; + + @override + Future setSize( + Size size, { + required int viewId, + required _ELinuxViewState viewState, + }) async { + assert(viewState != _ELinuxViewState.waitingForSize, + 'ELinux view must have an initial size. View id: $viewId'); + assert(!size.isEmpty); + + final Map? meta = + await SystemChannels.platform_views.invokeMapMethod( + 'resize', + { + 'id': viewId, + 'width': size.width, + 'height': size.height, + }, + ); + assert(meta != null); + assert(meta!.containsKey('width')); + assert(meta!.containsKey('height')); + return Size(meta!['width']! as double, meta['height']! as double); + } + + @override + Future setOffset( + Offset offset, { + required int viewId, + required _ELinuxViewState viewState, + }) async { + if (offset == _offset) { + return; + } + + // Don't set the offset unless the ELinux view has been created. + // The implementation of this method channel throws if the ELinux view for this viewId + // isn't addressable. + if (viewState != _ELinuxViewState.created) { + return; + } + + _offset = offset; + + await SystemChannels.platform_views.invokeMethod( + 'offset', + { + 'id': viewId, + 'top': offset.dy, + 'left': offset.dx, + }, + ); + } + + @override + Future sendDisposeMessage({required int viewId}) { + return SystemChannels.platform_views + .invokeMethod('dispose', { + 'id': viewId, + 'hybrid': false, + }); + } +} + +/// See: [_HybridAndroidViewControllerInternals] in `src/services/platform_view.dart` +class _HybridELinuxViewControllerInternals + extends _ELinuxViewControllerInternals { + @override + int get textureId { + throw UnimplementedError('Not supported for hybrid composition.'); + } + + @override + bool get requiresViewComposition => true; + + @override + Future setSize( + Size size, { + required int viewId, + required _ELinuxViewState viewState, + }) { + throw UnimplementedError('Not supported for hybrid composition.'); + } + + @override + Future setOffset( + Offset offset, { + required int viewId, + required _ELinuxViewState viewState, + }) { + throw UnimplementedError('Not supported for hybrid composition.'); + } + + @override + Future sendDisposeMessage({required int viewId}) { + return SystemChannels.platform_views + .invokeMethod('dispose', { + 'id': viewId, + 'hybrid': true, + }); + } +} diff --git a/packages/flutter_elinux/lib/src/widgets/platform_view.dart b/packages/flutter_elinux/lib/src/widgets/platform_view.dart new file mode 100644 index 0000000..16791a1 --- /dev/null +++ b/packages/flutter_elinux/lib/src/widgets/platform_view.dart @@ -0,0 +1,301 @@ +// Copyright 2023 Sony Group Corporation. All rights reserved. +// Copyright 2014 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:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import '../rendering/platform_view.dart'; +import '../services/platform_views.dart'; + +/// See: [AndroidView] in `src/widgets/platform_view.dart` +class ELinuxView extends StatefulWidget { + const ELinuxView({ + super.key, + required this.viewType, + this.onPlatformViewCreated, + this.hitTestBehavior = PlatformViewHitTestBehavior.opaque, + this.layoutDirection, + this.gestureRecognizers, + this.creationParams, + this.creationParamsCodec, + this.clipBehavior = Clip.hardEdge, + }) : assert(creationParams == null || creationParamsCodec != null); + + final String viewType; + final PlatformViewCreatedCallback? onPlatformViewCreated; + final PlatformViewHitTestBehavior hitTestBehavior; + final TextDirection? layoutDirection; + final Set>? gestureRecognizers; + final dynamic creationParams; + final MessageCodec? creationParamsCodec; + final Clip clipBehavior; + + @override + State createState() => _ELinuxViewState(); +} + +/// See: [_AndroidViewState] in `src/widgets/platform_view.dart` +class _ELinuxViewState extends State { + int? _id; + late ELinuxViewController _controller; + TextDirection? _layoutDirection; + bool _initialized = false; + FocusNode? _focusNode; + + static final Set> _emptyRecognizersSet = + >{}; + + @override + Widget build(BuildContext context) { + return Focus( + focusNode: _focusNode, + onFocusChange: _onFocusChange, + child: _ELinuxPlatformView( + controller: _controller, + hitTestBehavior: widget.hitTestBehavior, + gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet, + clipBehavior: widget.clipBehavior, + ), + ); + } + + void _initializeOnce() { + if (_initialized) { + return; + } + _initialized = true; + _createNewELinuxView(); + _focusNode = FocusNode(debugLabel: 'ELinuxView(id: $_id)'); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final TextDirection newLayoutDirection = _findLayoutDirection(); + final bool didChangeLayoutDirection = + _layoutDirection != newLayoutDirection; + _layoutDirection = newLayoutDirection; + + _initializeOnce(); + if (didChangeLayoutDirection) { + // The native view will update asynchronously, in the meantime we don't want + // to block the framework. (so this is intentionally not awaiting). + _controller.setLayoutDirection(_layoutDirection!); + } + } + + @override + void didUpdateWidget(ELinuxView oldWidget) { + super.didUpdateWidget(oldWidget); + + final TextDirection newLayoutDirection = _findLayoutDirection(); + final bool didChangeLayoutDirection = + _layoutDirection != newLayoutDirection; + _layoutDirection = newLayoutDirection; + + if (widget.viewType != oldWidget.viewType) { + //_controller.disposePostFrame(); + _controller.dispose(); + _createNewELinuxView(); + return; + } + + if (didChangeLayoutDirection) { + _controller.setLayoutDirection(_layoutDirection!); + } + } + + TextDirection _findLayoutDirection() { + assert( + widget.layoutDirection != null || debugCheckHasDirectionality(context)); + return widget.layoutDirection ?? Directionality.of(context); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode?.dispose(); + _focusNode = null; + super.dispose(); + } + + void _createNewELinuxView() { + _id = platformViewsRegistry.getNextPlatformViewId(); + _controller = PlatformViewsServiceELinux.initELinuxView( + id: _id!, + viewType: widget.viewType, + layoutDirection: _layoutDirection!, + creationParams: widget.creationParams, + creationParamsCodec: widget.creationParamsCodec, + onFocus: () { + _focusNode!.requestFocus(); + }, + ); + if (widget.onPlatformViewCreated != null) { + _controller + .addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!); + } + } + + void _onFocusChange(bool isFocused) { + if (!_controller.isCreated) { + return; + } + if (!isFocused) { + _controller.clearFocus().catchError((dynamic e) { + if (e is MissingPluginException) { + return; + } + }); + return; + } + SystemChannels.textInput.invokeMethod( + 'TextInput.setPlatformViewClient', + {'platformViewId': _id}, + ).catchError((dynamic e) { + if (e is MissingPluginException) { + return; + } + }); + } +} + +/// See: [_AndroidPlatformView] in `src/widgets/platform_view.dart` +class _ELinuxPlatformView extends LeafRenderObjectWidget { + const _ELinuxPlatformView({ + required this.controller, + required this.hitTestBehavior, + required this.gestureRecognizers, + this.clipBehavior = Clip.hardEdge, + }); + + final ELinuxViewController controller; + final PlatformViewHitTestBehavior hitTestBehavior; + final Set> gestureRecognizers; + final Clip clipBehavior; + + @override + RenderObject createRenderObject(BuildContext context) => RenderELinuxView( + viewController: controller, + hitTestBehavior: hitTestBehavior, + gestureRecognizers: gestureRecognizers, + clipBehavior: clipBehavior, + ); + + @override + void updateRenderObject(BuildContext context, RenderELinuxView renderObject) { + renderObject.controller = controller; + renderObject.hitTestBehavior = hitTestBehavior; + renderObject.updateGestureRecognizers(gestureRecognizers); + renderObject.clipBehavior = clipBehavior; + } +} + +/// See: [AndroidViewSurface] in `src/widgets/platform_view.dart` +class ELinuxViewSurface extends StatefulWidget { + const ELinuxViewSurface({ + super.key, + required this.controller, + required this.hitTestBehavior, + required this.gestureRecognizers, + }); + + final ELinuxViewController controller; + final Set> gestureRecognizers; + final PlatformViewHitTestBehavior hitTestBehavior; + + @override + State createState() { + return _ELinuxViewSurfaceState(); + } +} + +/// See: [AndroidViewSurfaceState] in `src/widgets/platform_view.dart` +class _ELinuxViewSurfaceState extends State { + @override + void initState() { + super.initState(); + if (!widget.controller.isCreated) { + widget.controller + .addOnPlatformViewCreatedListener(_onPlatformViewCreated); + } + } + + @override + void dispose() { + widget.controller + .removeOnPlatformViewCreatedListener(_onPlatformViewCreated); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (widget.controller.requiresViewComposition) { + return _PlatformLayerBasedELinuxViewSurface( + controller: widget.controller, + hitTestBehavior: widget.hitTestBehavior, + gestureRecognizers: widget.gestureRecognizers, + ); + } else { + return _TextureBasedELinuxViewSurface( + controller: widget.controller, + hitTestBehavior: widget.hitTestBehavior, + gestureRecognizers: widget.gestureRecognizers, + ); + } + } + + void _onPlatformViewCreated(int _) { + setState(() {}); + } +} + +/// See: [_TextureBasedAndroidViewSurface] in `src/widgets/platform_view.dart` +class _TextureBasedELinuxViewSurface extends PlatformViewSurface { + const _TextureBasedELinuxViewSurface({ + required ELinuxViewController super.controller, + required super.hitTestBehavior, + required super.gestureRecognizers, + }); + + @override + RenderObject createRenderObject(BuildContext context) { + final ELinuxViewController viewController = + controller as ELinuxViewController; + // Use GL texture based composition. + // App should use GL texture unless they require to embed a SurfaceView. + final RenderELinuxView renderBox = RenderELinuxView( + viewController: viewController, + gestureRecognizers: gestureRecognizers, + hitTestBehavior: hitTestBehavior, + ); + viewController.pointTransformer = + (Offset position) => renderBox.globalToLocal(position); + return renderBox; + } +} + +/// See: [_PlatformLayerBasedAndroidViewSurface] in `src/widgets/platform_view.dart` +class _PlatformLayerBasedELinuxViewSurface extends PlatformViewSurface { + const _PlatformLayerBasedELinuxViewSurface({ + required ELinuxViewController super.controller, + required super.hitTestBehavior, + required super.gestureRecognizers, + }); + + @override + RenderObject createRenderObject(BuildContext context) { + final ELinuxViewController viewController = + controller as ELinuxViewController; + final PlatformViewRenderBox renderBox = + super.createRenderObject(context) as PlatformViewRenderBox; + viewController.pointTransformer = + (Offset position) => renderBox.globalToLocal(position); + return renderBox; + } +} diff --git a/packages/flutter_elinux/lib/widgets.dart b/packages/flutter_elinux/lib/widgets.dart new file mode 100644 index 0000000..7e57343 --- /dev/null +++ b/packages/flutter_elinux/lib/widgets.dart @@ -0,0 +1,8 @@ +// Copyright 2023 Sony Group Corporation. All rights reserved. +// Copyright 2014 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. + +export 'src/rendering/platform_view.dart'; +export 'src/services/platform_views.dart'; +export 'src/widgets/platform_view.dart'; diff --git a/packages/flutter_elinux/pubspec.yaml b/packages/flutter_elinux/pubspec.yaml new file mode 100644 index 0000000..69092bf --- /dev/null +++ b/packages/flutter_elinux/pubspec.yaml @@ -0,0 +1,13 @@ +name: flutter_elinux +description: Flutter packages for flutter-elinux +version: 0.1.0 +homepage: https://github.com/sony/flutter-elinux +repository: https://github.com/sony/flutter-elinux/tree/main/packages/flutter_elinux + +environment: + sdk: ">=2.18.0 <4.0.0" + flutter: ">=3.3.0" + +dependencies: + flutter: + sdk: flutter