// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// 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 .
//! The effect library panel (ζζεΊ): every effect type the engine can add
//! to a clip's chain, as a flat list. Double-clicking an entry appends the
//! effect to the selected clip's effect chain (the undoable
//! [`AppEngine::add_effect`]; the insertion index is clamped to the chain
//! end by the backend).
use gpui::colors::DefaultColors;
use gpui::dock::{DockPanel, PanelEvent};
use gpui::{
div, prelude::*, AnyElement, App, ClickEvent, Context, Entity, EventEmitter, MouseButton,
Render, SharedString, Window,
};
use gpui_elements::editable_text::{EditableTextState, StringStorage, TextChanged};
use crate::oakui::component::text_input;
use crate::i18n;
use crate::oakui::AppEngine;
use crate::panels::commands::PanelCommandHandler;
use crate::panels::ids::EFFECT_LIBRARY;
/// The effect library panel.
pub struct EffectLibraryPanel {
engine: Entity,
/// The search box state: live-filters the list by name / type id
/// (case-insensitive substring).
search: Entity,
}
impl EffectLibraryPanel {
/// Builds the panel over `engine`'s addable-effect table.
pub fn new(engine: Entity, _window: &mut Window, cx: &mut Context) -> Self {
// Re-read the effect table whenever the engine notifies (the table
// itself is static, but the selection hint depends on the target).
cx.observe(&engine, |_this, _engine, cx| cx.notify()).detach();
let search = cx.new(|cx| EditableTextState::new(StringStorage::default(), cx));
cx.subscribe(&search, |_this, _state, _event: &TextChanged, cx| {
cx.notify();
})
.detach();
Self { engine, search }
}
}
/// The effect library implements no focused-panel commands: everything
/// falls through to the shell's global handler.
impl PanelCommandHandler for EffectLibraryPanel {}
impl Render for EffectLibraryPanel {
fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement {
let colors = cx.default_colors().clone();
let effects = self.engine.read(cx).addable_effects();
let query = self.search.read(cx).as_str().trim().to_lowercase();
let mut list = div()
.id("effect-library-list")
.flex_1()
.min_h_0()
.flex()
.flex_col()
.gap_1()
.p_2()
.overflow_y_scroll();
// Built-in effects render flat under a Built-in header; OpenFX
// plugin entries are grouped under their sub-category header
// (Filter / Generator / Transition / General β the C++
// `factorymenu` OpenFX branch). The engine table arrives sorted
// (built-ins first, then groups and names alphabetically); the
// search box live-filters by name / type id.
let mut last_group: Option