/*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team Modifications Copyright (C) 2025 mikesolar This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #ifndef OAK_VIEWERGLWIDGET_H #define OAK_VIEWERGLWIDGET_H #include #include #include #include "engineeventbridge.h" #include "oakengine/traverse.h" #include "oakutil/oakvideo.h" #include "viewerplaybacktimer.h" #include "viewerqueue.h" #include "viewersafemargininfo.h" #include "viewertexteditor.h" #include "widget/manageddisplay/manageddisplay.h" #include "widget/timetarget/timetarget.h" #include "widget/viewer/displaybuffer.h" #include "widget/viewer/vieweroutpututils.h" namespace olive { /** * @brief The inner display/rendering widget of a Viewer class. * * Actual composition occurs elsewhere offscreen and * multithreaded, so its main purpose is receiving a finalized OpenGL texture and displaying it. * * The main entry point is SetTexture() which will receive an OpenGL texture ID, store it, and then call update() to * draw it on screen. The drawing function is in paintGL() (called during the update() process by Qt) and is fairly * simple OpenGL drawing code standardized around OpenGL ES 3.2 Core. * * If the texture has been modified and you're 100% sure this widget is using the same texture object, it's possible * to call update() directly to trigger a repaint, however this is not recommended. If you are not 100% sure it'll be * the same texture object, use SetTexture() since it will nearly always be faster to just set it than to check *and* * set it. */ class ViewerDisplayWidget : public ManagedDisplayWidget, public TimeTargetObject { Q_OBJECT public: /** * @brief ViewerGLWidget Constructor * * @param parent * * QWidget parent. */ ViewerDisplayWidget(QWidget *parent = nullptr); virtual ~ViewerDisplayWidget() override; const ViewerSafeMarginInfo &get_safe_margin() const; void set_safe_margins(const ViewerSafeMarginInfo &safe_margin); void set_gizmos(OakEngineNode *node); const oak::VideoParams &get_video_params() const { return gizmo_params_; } void set_video_params(const oak::VideoParams ¶ms); const AudioParams &get_audio_params() const { return gizmo_audio_params_; } void set_audio_params(const AudioParams &p); void set_time(const Rational &time); void set_subtitle_tracks(OakEngineSequence *list); void set_show_widget_background(bool e) { show_widget_background_ = e; update(); } /** * @brief Transform a point from viewer space to the buffer space. * Multiplies by the inverted transform matrix to undo the scaling and translation. */ QPointF transform_viewer_space_to_buffer_space(const QPointF &pos); bool is_deinterlacing() const { return deinterlace_; } void reset_fps_timer(); bool get_show_fps() const { return show_fps_; } bool get_show_subtitles() const { return show_subtitles_; } void set_show_subtitles(bool e) { show_subtitles_ = e; update(); } void increment_skipped_frames(); void increment_frame_count() { fps_timer_update_count_++; } void *get_current_texture() const { return texture_; } ColorProcessorHandlePtr get_current_color_processor() { return color_service(); } void play(const int64_t &start_timestamp, const int &playback_speed, const Rational &timebase, bool start_updating); void pause(); ViewerQueue *queue() { return &queue_; } ViewerPlaybackTimer *timer() { return &timer_; } QPointF screen_to_scene_point(const QPoint &p); virtual bool eventFilter(QObject *o, QEvent *e) override; public slots: /** * @brief Set the transformation matrix to draw with * * Set this if you want the drawing to pass through some sort of transform (most of the time you won't want this). */ void set_matrix_translate(const QMatrix4x4 &mat); /** * @brief Set the scale matrix. */ void set_matrix_zoom(const QMatrix4x4 &mat); void set_matrix_crop(const QMatrix4x4 &mat); /** * @brief Enables or disables whether this color at the cursor should be emitted * * Since tracking the mouse every movement, reading pixels, and doing color transforms are processor intensive, we * have an option for it. Ideally, this should be connected to a PixelSamplerPanel::visibilityChanged signal so that * it can automatically be enabled when the user is pixel sampling and disabled for optimization when they're not. */ void set_signal_cursor_color_enabled(bool e); void set_image(const QVariant &buffer); void set_blank(); /** * @brief Changes the pointer type if the tool is changed to the hand tool. Otherwise resets the pointer to it's * normal type. */ void update_cursor(); void tool_changed(); /** * @brief Enables/disables a basic deinterlace on the viewer */ void set_deinterlacing(bool e); void set_show_fps(bool e); void request_start_editing_text(); signals: /** * @brief Signal emitted when the user starts dragging from the viewer */ void drag_started(const QPoint &p); /** * @brief Signal emitted when a hand drag starts */ void hand_drag_started(); /** * @brief Signal emitted when a hand drag moves */ void hand_drag_moved(int x, int y); /** * @brief Signal emitted when a hand drag ends */ void hand_drag_ended(); /** * @brief Signal emitted when cursor color is enabled and the user's mouse position changes */ void cursor_color(const Color &reference, const Color &display); void drag_entered(QDragEnterEvent *event); void drag_left(QDragLeaveEvent *event); void dropped(QDropEvent *event); void texture_changed(void *texture); void queue_starved(); void queue_no_longer_starved(); void create_addable_at(const QRectF &rect); protected: QTransform generate_world_transform(); QTransform generate_display_transform(); QTransform generate_gizmo_transform(OakEngineNode *gizmos, OakEngineNode *target, const TimeRange &range); QTransform generate_gizmo_transform() { TimeRange range = generate_gizmo_time(); return generate_gizmo_transform(gizmos_, get_time_target(), range); } TimeRange generate_gizmo_time() { Rational node_time = get_gizmo_time(); return TimeRange(node_time, node_time + gizmo_params_.frame_rate_as_time_base()); } virtual void *load_custom_texture_from_frame(const QVariant &v) { return nullptr; } protected slots: /** * @brief Paint function to display the texture (received in SetTexture()) on screen. * * Simple OpenGL drawing function for painting the texture on screen. Standardized around OpenGL ES 3.2 Core. */ virtual void on_paint() override; virtual void on_destroy() override; private: QPointF get_texture_position(const QPoint &screen_pos); QPointF get_texture_position(const QSize &size); QPointF get_texture_position(const double &x, const double &y); static void draw_text_with_crude_shadow(QPainter *painter, const QRect &rect, const QString &text, const QTextOption &opt = QTextOption()); Rational get_gizmo_time(); bool is_hand_drag(QMouseEvent *event) const; void update_matrix(); void *try_gizmo_press(const QPointF &p); void open_text_gizmo(void *text, QMouseEvent *event = nullptr); bool on_mouse_press(QMouseEvent *e); bool on_mouse_move(QMouseEvent *e); bool on_mouse_release(QMouseEvent *e); bool on_mouse_double_click(QMouseEvent *e); bool on_key_press(QKeyEvent *e); bool on_key_release(QKeyEvent *e); void emit_color_at_cursor(QMouseEvent *e); void draw_subtitle_tracks(); QPointF get_virtual_pos_for_text_edit(const QPointF &p) { return text_transform_inverted_.map(p) - text_edit_pos_; } template void forward_drag_event_to_text_edit(T *event); bool forward_mouse_event_to_text_edit(QMouseEvent *event, bool check_if_outside = false); bool forward_event_to_text_edit(QEvent *event); QPointF adjust_pos_by_v_align(QPointF p); void close_text_editor(); void generate_gizmo_transforms(); void draw_blank(const oak_video_params &device_params); void draw_backend_neutral(const oak_color_transform_job &ctj, QPainter *painter); bool draw_backend_neutral_frame(void *frame, QPainter *painter); bool draw_backend_neutral_texture(void *texture, QPainter *painter); /** * @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL(). * * Never assign directly; use assign_texture() so ownership stays consistent. */ void *texture_; /** * @brief Owned (retained or created) reference backing texture_. * * texture_ may point to a texture owned by the playback queue's * OakSharedBuffer; this member holds our own reference so texture_ can * never dangle after the queue entry is replaced. */ void *owned_texture_ = nullptr; /** * @brief Assign texture_ with consistent ownership. * * Releases the previous owned reference. If owned is true, takes over the * passed reference (e.g. freshly created); otherwise retains it. texture_ * is set to the same pointer (possibly nullptr). */ void assign_texture(void *t, bool owned); /** * @brief Internal texture to deinterlace to */ void *deinterlace_texture_; /** * @brief Offscreen texture for backend-neutral viewer rendering. * * The texture is rendered at device resolution and read back to a QImage so * it can be painted with QPainter on the plain QWidget inner surface. */ void *backend_neutral_texture_; /** * @brief CPU readback buffer for backend_neutral_texture_. */ QByteArray backend_neutral_buffer_; QImage backend_neutral_cpu_image_; void *backend_neutral_cpu_display_frame_; void *backend_neutral_cpu_source_frame_; void *backend_neutral_cpu_source_texture_; QString backend_neutral_cpu_color_id_; /** * @brief Deinterlace shader */ void *deinterlace_shader_; /** * @brief Blank shader */ void *blank_shader_; /** * @brief Translation only matrix (defaults to identity). */ QMatrix4x4 translate_matrix_; /** * @brief Scale only matrix. */ QMatrix4x4 scale_matrix_; /** * @brief Crop only matrix */ QMatrix4x4 crop_matrix_; /** * @brief Cached result of translate_matrix_ and scale_matrix_ multiplied */ QMatrix4x4 combined_matrix_; QMatrix4x4 combined_matrix_flipped_; bool signal_cursor_color_; ViewerSafeMarginInfo safe_margin_; OakEngineNode *gizmos_; /** * @brief Opaque storage for the engine's NodeValueRow * (QHash, engine/node/value.h). * * WRAPPER-GAP: NodeValue is engine-internal, so the real row typedef * cannot be declared app-side, and the C ABI offers no row create/free * pair — the row is only filled in place by * oakengine_traverse_generate_row() and handed back to the engine * (oakengine_node_update_gizmo_positions()/oakengine_gizmo_drag_start()). * Qt 6 QHash is a single-pointer class for any template arguments, so a * nullptr-initialized pointer is byte-identical to an empty row; the * engine's real QHash (move-)assignment manages the contents from then * on. The row held at widget destruction leaks (one row per widget) * until the C ABI grows a create/free pair. */ void *gizmo_db_ = nullptr; oak::VideoParams gizmo_params_; AudioParams gizmo_audio_params_; QPoint gizmo_start_drag_; QPoint gizmo_last_drag_; TimeRange gizmo_draw_time_; void *current_gizmo_; bool gizmo_drag_started_; QTransform gizmo_last_draw_transform_; QTransform gizmo_last_draw_transform_inverted_; bool show_subtitles_; OakEngineSequence *subtitle_tracks_; EngineEventBridge *bridge_; int64_t subtitle_sub_ = 0; Rational time_; /** * @brief Position of mouse to calculate delta from. */ QPoint hand_last_drag_pos_; bool hand_dragging_; bool deinterlace_; qint64 fps_timer_start_; int fps_timer_update_count_; bool show_fps_; int frames_skipped_; QVector frame_rate_averages_; int frame_rate_average_count_; bool show_widget_background_; QVariant load_frame_; int playback_speed_; enum PushMode { /// New frame to push to internal texture k_push_frame, /// Internal texture reference is up to date, keep showing it k_push_unnecessary, /// Draw blank/black screen k_push_blank, /// Draw nothing (not even a black frame) k_push_null, }; PushMode push_mode_; // Playback ViewerQueue queue_; ViewerPlaybackTimer timer_; Rational playback_timebase_; bool add_band_; QPoint add_band_start_; QPoint add_band_end_; bool queue_starved_; void *active_text_gizmo_; QPointF text_edit_pos_; ViewerTextEditor *text_edit_; ViewerTextEditorToolBar *text_toolbar_; QTransform text_transform_; QTransform text_transform_inverted_; private slots: void update_from_queue(); void text_edit_changed(); void text_edit_destroyed(); void subtitles_changed(const TimeRange &r); void focus_changed(QWidget *old, QWidget *now); QRectF update_active_text_gizmo_size(); }; } #endif // OAK_VIEWERGLWIDGET_H