#include "pch.h" #include "app.h" #include "app_core/app_frame.h" #include "app_core/app_input.h" #include "app_core/document_platform_io.h" #include "app_core/document_sharing.h" #include "platform_api/platform_services.h" #include "platform_legacy/legacy_platform_services.h" #include "renderer_gl/opengl_capabilities.h" namespace { [[nodiscard]] GLint rgba8_internal_format() noexcept { return static_cast(pp::renderer::gl::rgba8_internal_format()); } [[nodiscard]] bool should_dispatch_keyboard_visibility(bool visible) noexcept { const auto action = pp::app::plan_virtual_keyboard(visible); return visible ? action == pp::app::VirtualKeyboardAction::show_keyboard : action == pp::app::VirtualKeyboardAction::hide_keyboard; } [[nodiscard]] bool should_dispatch_cursor_visibility(bool visible) noexcept { const auto action = pp::app::plan_cursor_visibility(visible); return visible ? action == pp::app::CursorVisibilityAction::show_cursor : action == pp::app::CursorVisibilityAction::hide_cursor; } } namespace { [[nodiscard]] pp::platform::PlatformServices& active_platform_services() { if (App::I) { if (auto* services = App::I->platform_services()) return *services; } return pp::platform::legacy::platform_services(); } } void App::set_platform_services(pp::platform::PlatformServices* services) noexcept { platform_services_ = services; } pp::platform::PlatformServices* App::platform_services() const noexcept { return platform_services_; } pp::platform::PlatformStoragePaths App::prepare_storage_paths() { return active_platform_services().prepare_storage_paths(); } std::string App::clipboard_get_text() { if (pp::app::plan_clipboard_read() != pp::app::ClipboardReadAction::read_text) return {}; return active_platform_services().clipboard_text(); } bool App::clipboard_set_text(const std::string& s) { if (pp::app::plan_clipboard_write(s) != pp::app::ClipboardWriteAction::write_text) return false; return active_platform_services().set_clipboard_text(s); } void App::stacktrace() { active_platform_services().log_stacktrace(); } void App::crash_test() { active_platform_services().trigger_crash_test(); } void App::tick(float dt) { const auto tick_plan = pp::app::plan_app_frame_tick( layout_designer.get(main_id) != nullptr, layout.get(main_id) != nullptr); if (auto* main = layout_designer[main_id]; tick_plan.tick_designer_layout && main) main->tick(dt); if (auto* main = layout[main_id]; tick_plan.tick_main_layout && main) main->tick(dt); } void App::resize(float w, float h) { LOG("App::resize %d %d", (int)w, (int)h); const auto resize_plan = pp::app::plan_app_resize(w, h); if (!resize_plan) { LOG("App resize plan failed: %s", resize_plan.status().message); return; } if (resize_plan.value().recreate_ui_render_target) { uirtt.create( resize_plan.value().render_target_width, resize_plan.value().render_target_height, -1, rgba8_internal_format(), true); } redraw = resize_plan.value().request_redraw; width = resize_plan.value().width; height = resize_plan.value().height; } void App::show_cursor() { if (!should_dispatch_cursor_visibility(true)) return; active_platform_services().set_cursor_visible(true); } void App::hide_cursor() { if (!should_dispatch_cursor_visibility(false)) return; active_platform_services().set_cursor_visible(false); } void App::showKeyboard() { LOG("show keyboard"); redraw = true; if (!should_dispatch_keyboard_visibility(true)) return; active_platform_services().set_virtual_keyboard_visible(true); } void App::hideKeyboard() { LOG("hide keyboard"); redraw = true; if (!should_dispatch_keyboard_visibility(false)) return; active_platform_services().set_virtual_keyboard_visible(false); } void App::pick_image(std::function callback) { redraw = true; active_platform_services().pick_image(std::move(callback)); } void App::pick_file(std::vector types, std::function callback) { redraw = true; active_platform_services().pick_file(std::move(types), std::move(callback)); } void App::pick_file_save(const std::string& type, const std::string& default_name, std::function writer, std::function callback) { redraw = true; const auto target = active_platform_services().prepare_writable_file(type, default_name, data_path, tmp_path); if (target.path.empty()) { callback({}, false); return; } LOG("App::pick_file_save %s", target.path.c_str()); auto write_and_save = [=] { writer(target.path); save_prepared_file(target.path, target.suggested_name, callback); }; if (target.write_on_background_thread) std::thread(write_and_save).detach(); else write_and_save(); } void App::pick_file_save(std::vector types, std::function callback) { redraw = true; active_platform_services().pick_save_file(std::move(types), std::move(callback)); } bool App::uses_prepared_file_writes() const { return active_platform_services().uses_prepared_file_writes(); } bool App::uses_work_directory_document_export_collections() const { return active_platform_services().uses_work_directory_document_export_collections(); } bool App::disables_network_tls_verification() const { return active_platform_services().disables_network_tls_verification(); } bool App::uses_ppbr_export_data_directory_override() const { return active_platform_services().uses_ppbr_export_data_directory_override(); } bool App::platform_supports_sonarpen() const { return active_platform_services().supports_sonarpen(); } void App::start_platform_sonarpen() { active_platform_services().start_sonarpen(); } int App::default_canvas_resolution() const { return active_platform_services().default_canvas_resolution(); } bool App::draws_canvas_tip_for_input(kEventSource source, kEventType type) const { return active_platform_services().draws_canvas_tip_for_pointer( source == kEventSource::Mouse, source == kEventSource::Stylus, type == kEventType::MouseUpL); } float App::adjust_canvas_input_pressure(float pressure) const { return active_platform_services().adjust_canvas_input_pressure(pressure); } void App::pick_dir(std::function callback) { redraw = true; active_platform_services().pick_directory(std::move(callback)); } bool App::supports_working_directory_picker() const { return active_platform_services().supports_working_directory_picker(); } std::string App::format_working_directory_path(std::string_view path) const { return active_platform_services().format_working_directory_path(path); } void App::display_file(std::string path) { if (pp::app::plan_display_file(path) == pp::app::DisplayFileAction::ignore_empty_path) return; active_platform_services().display_file(path); } void App::share_file(std::string path) { const auto plan = pp::app::plan_document_share(path); if (plan == pp::app::DocumentShareAction::show_save_required_warning) { message_box("Sharing failed", "Please save the document before sharing it."); return; } active_platform_services().share_file(path); } void App::request_app_close() { active_platform_services().request_app_close(); } bool App::start_platform_vr_mode() { return active_platform_services().start_vr_mode(); } void App::stop_platform_vr_mode() { active_platform_services().stop_vr_mode(); } void App::attach_ui_thread() { active_platform_services().attach_ui_thread(); } void App::detach_ui_thread() { active_platform_services().detach_ui_thread(); } void App::acquire_render_context() { active_platform_services().acquire_render_context(); } void App::release_render_context() { active_platform_services().release_render_context(); } void App::present_render_context() { active_platform_services().present_render_context(); } void App::bind_default_render_target() { active_platform_services().bind_default_render_target(); } void App::bind_main_render_target() { active_platform_services().bind_main_render_target(); } void App::apply_render_platform_hints() { active_platform_services().apply_render_platform_hints(); } void App::install_render_debug_callback() { active_platform_services().install_render_debug_callback(); } void App::begin_render_capture_frame() { active_platform_services().begin_render_capture_frame(); } void App::end_render_capture_frame() { active_platform_services().end_render_capture_frame(); } bool App::platform_deletes_recorded_files_on_clear() { return active_platform_services().deletes_recorded_files_on_clear(); } void App::clear_platform_recorded_files(std::string path) { active_platform_services().clear_recorded_files(path); } void App::publish_exported_image(std::string path) { active_platform_services().publish_exported_image(path); } void App::flush_platform_storage() { active_platform_services().flush_persistent_storage(); } std::vector App::document_browse_roots() const { return active_platform_services().document_browse_roots(work_path, data_path); } void App::save_platform_ui_state() { active_platform_services().save_ui_state(); } bool App::platform_enables_live_asset_reloading() { return active_platform_services().enables_live_asset_reloading(); } void App::update_platform_frame(float delta_time_seconds) { active_platform_services().update_platform_frame(delta_time_seconds); } void App::report_rendered_frames(int frames) { active_platform_services().report_rendered_frames(frames); } void App::save_prepared_file( std::string path, std::string suggested_name, std::function callback) { active_platform_services().save_prepared_file( path, suggested_name, [callback = std::move(callback)](std::string saved_path, bool saved) { callback(saved_path, saved); }); } bool App::mouse_down(int button, float x, float y, float pressure, kEventSource source, bool eraser) { const auto plan = pp::app::plan_app_pointer_dispatch( x, y, zoom, layout_designer.get(main_id) != nullptr, layout.get(main_id) != nullptr); if (!plan) { LOG("Mouse down dispatch plan failed: %s", plan.status().message); return false; } redraw = plan.value().request_redraw; MouseEvent e; e.m_type = button ? kEventType::MouseDownR : kEventType::MouseDownL; e.m_pos = { plan.value().normalized_x, plan.value().normalized_y }; e.m_pressure = pressure; e.m_source = source; e.m_eraser = eraser; kEventResult ret = kEventResult::Available; if (auto* main = layout_designer[main_id]; plan.value().dispatch_designer_first && main) return main->on_event(&e) == kEventResult::Consumed; if (auto* main = layout[main_id]; plan.value().dispatch_main_if_not_consumed && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } bool App::mouse_move(float x, float y, float pressure, kEventSource source, bool eraser) { const auto plan = pp::app::plan_app_pointer_dispatch( x, y, zoom, layout_designer.get(main_id) != nullptr, layout.get(main_id) != nullptr); if (!plan) { LOG("Mouse move dispatch plan failed: %s", plan.status().message); return false; } cursor = { plan.value().normalized_x, plan.value().normalized_y }; redraw = plan.value().request_redraw; MouseEvent e; e.m_type = kEventType::MouseMove; e.m_pos = { plan.value().normalized_x, plan.value().normalized_y }; e.m_pressure = pressure; e.m_source = source; e.m_eraser = eraser; kEventResult ret = kEventResult::Available; if (auto* main = layout_designer[main_id]; plan.value().dispatch_designer_first && main) return main->on_event(&e) == kEventResult::Consumed; if (auto* main = layout[main_id]; plan.value().dispatch_main_if_not_consumed && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } bool App::mouse_up(int button, float x, float y, kEventSource source, bool eraser) { const auto plan = pp::app::plan_app_pointer_dispatch( x, y, zoom, layout_designer.get(main_id) != nullptr, layout.get(main_id) != nullptr); if (!plan) { LOG("Mouse up dispatch plan failed: %s", plan.status().message); return false; } redraw = plan.value().request_redraw; MouseEvent e; e.m_type = button ? kEventType::MouseUpR : kEventType::MouseUpL; e.m_pos = { plan.value().normalized_x, plan.value().normalized_y }; e.m_source = source; e.m_eraser = eraser; kEventResult ret = kEventResult::Available; if (auto* main = layout_designer[main_id]; plan.value().dispatch_designer_first && main) return main->on_event(&e) == kEventResult::Consumed; if (auto* main = layout[main_id]; plan.value().dispatch_main_if_not_consumed && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } bool App::mouse_scroll(float x, float y, float delta) { const auto plan = pp::app::plan_app_pointer_dispatch( x, y, zoom, layout_designer.get(main_id) != nullptr, layout.get(main_id) != nullptr); if (!plan) { LOG("Mouse scroll dispatch plan failed: %s", plan.status().message); return false; } redraw = plan.value().request_redraw; MouseEvent e; e.m_type = kEventType::MouseScroll; e.m_pos = { plan.value().normalized_x, plan.value().normalized_y }; e.m_scroll_delta = delta; kEventResult ret = kEventResult::Available; if (auto* main = layout_designer[main_id]; plan.value().dispatch_designer_first && main) return main->on_event(&e) == kEventResult::Consumed; if (auto* main = layout[main_id]; plan.value().dispatch_main_if_not_consumed && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } bool App::mouse_cancel(int button) { const auto plan = pp::app::plan_app_mouse_cancel_dispatch( layout_designer.get(main_id) != nullptr, layout.get(main_id) != nullptr); redraw = plan.request_redraw; MouseEvent e; e.m_type = kEventType::MouseCancel; kEventResult ret = kEventResult::Available; if (auto* main = layout_designer[main_id]; plan.dispatch_designer_first && main) return main->on_event(&e) == kEventResult::Consumed; if (auto* main = layout[main_id]; plan.dispatch_main_if_not_consumed && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } bool App::gesture_start(const glm::vec2& p0, const glm::vec2& p1) { const auto plan = pp::app::plan_app_gesture_dispatch( p0.x, p0.y, p1.x, p1.y, p0.x, p0.y, p1.x, p1.y, zoom, layout.get(main_id) != nullptr); if (!plan) { LOG("Gesture start dispatch plan failed: %s", plan.status().message); return false; } redraw = plan.value().request_redraw; GestureEvent e; e.m_type = kEventType::GestureStart; e.m_pos = { plan.value().normalized_x, plan.value().normalized_y }; e.m_distance = plan.value().distance; gesture_p0 = p0; gesture_p1 = p1; kEventResult ret = kEventResult::Available; if (auto* main = layout[main_id]; plan.value().dispatch_main && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } bool App::gesture_move(const glm::vec2& p0, const glm::vec2& p1) { const auto plan = pp::app::plan_app_gesture_dispatch( p0.x, p0.y, p1.x, p1.y, gesture_p0.x, gesture_p0.y, gesture_p1.x, gesture_p1.y, zoom, layout.get(main_id) != nullptr); if (!plan) { LOG("Gesture move dispatch plan failed: %s", plan.status().message); return false; } redraw = plan.value().request_redraw; GestureEvent e; e.m_type = kEventType::GestureMove; e.m_pos = { plan.value().normalized_x, plan.value().normalized_y }; e.m_distance = plan.value().distance; e.m_distance_delta = plan.value().distance_delta; e.m_pos_delta = { plan.value().position_delta_x, plan.value().position_delta_y }; kEventResult ret = kEventResult::Available; if (auto* main = layout[main_id]; plan.value().dispatch_main && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } bool App::gesture_end() { const auto plan = pp::app::plan_app_main_input_dispatch(layout.get(main_id) != nullptr); redraw = plan.request_redraw; GestureEvent e; e.m_type = kEventType::GestureEnd; kEventResult ret = kEventResult::Available; if (auto* main = layout[main_id]; plan.dispatch_main && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } bool App::touch_tap(const glm::vec2& pos, int fingers, int tap_count) { const auto plan = pp::app::plan_app_main_input_dispatch(layout.get(main_id) != nullptr); redraw = plan.request_redraw; TouchEvent e; e.m_type = kEventType::TouchTap; e.m_finger_count = fingers; e.m_tap_count = tap_count; kEventResult ret = kEventResult::Available; if (auto* main = layout[main_id]; plan.dispatch_main && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } bool App::key_down(kKey key) { const auto plan = pp::app::plan_app_key_down_dispatch( layout.get(main_id) != nullptr, key == kKey::KeySpacebar, vr_active); if (plan.sync_vr_camera_rotation) canvas->m_canvas->m_cam_rot = vr_rot; redraw = plan.request_redraw; keys[(int)key] = plan.set_key_down; KeyEvent e; e.m_type = kEventType::KeyDown; e.m_key = key; kEventResult ret = kEventResult::Available; if (auto* main = layout[main_id]; plan.dispatch_main && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } bool App::key_up(kKey key) { const auto plan = pp::app::plan_app_key_up_dispatch(layout.get(main_id) != nullptr); redraw = plan.request_redraw; keys[(int)key] = plan.set_key_down; KeyEvent e; e.m_type = kEventType::KeyUp; e.m_key = key; kEventResult ret = kEventResult::Available; if (auto* main = layout[main_id]; plan.dispatch_main && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } bool App::key_char(char key) { const auto plan = pp::app::plan_app_main_input_dispatch(layout.get(main_id) != nullptr); redraw = plan.request_redraw; KeyEvent e; e.m_type = kEventType::KeyChar; e.m_char = key; kEventResult ret = kEventResult::Available; if (auto* main = layout[main_id]; plan.dispatch_main && main) ret = main->on_event(&e); return ret == kEventResult::Consumed; } void App::toggle_ui() { auto* main = layout[main_id]; const std::size_t main_child_count = main ? main->m_children.size() : 0U; auto* panel_container = main_child_count > 1U ? main->m_children[1].get() : nullptr; const auto plan = pp::app::plan_app_ui_visibility_toggle( ui_visible, main != nullptr, main_child_count, panel_container ? panel_container->m_children.size() : 0U); if (!plan) { LOG("UI toggle plan failed: %s", plan.status().message); return; } ui_visible = plan.value().next_ui_visible; if (!panel_container) return; for (std::size_t i = plan.value().first_panel_child_index; i < plan.value().panel_child_count; ++i) { panel_container->m_children[i]->m_display = ui_visible; } } void App::set_stylus() { const auto plan = pp::app::plan_app_stylus_attach(canvas != nullptr); has_stylus = plan.set_has_stylus; if (plan.enable_canvas_touch_lock && canvas) canvas->m_canvas->m_touch_lock = true; }