use crate::common::LeakManager;
use aviutl2_sys::plugin2::EDIT_SECTION;
use std::mem::MaybeUninit;
use std::{ffi::c_void, ptr::NonNull};
pub trait FilterConfigItems: Sized {
fn to_config_items() -> Vec<FilterConfigItem>;
fn from_config_items(items: &[FilterConfigItem]) -> Self;
}
#[doc(inline)]
pub use aviutl2_macros::filter_config_items;
pub trait FilterConfigItemSliceExt {
fn to_struct<T: FilterConfigItems>(&self) -> T;
}
impl FilterConfigItemSliceExt for &[FilterConfigItem] {
fn to_struct<T: FilterConfigItems>(&self) -> T {
T::from_config_items(self)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum FilterConfigItem {
Track(FilterConfigTrack),
Checkbox(FilterConfigCheckbox),
Color(FilterConfigColor),
Select(FilterConfigSelect),
File(FilterConfigFile),
String(FilterConfigString),
Text(FilterConfigText),
Folder(FilterConfigFolder),
Data(ErasedFilterConfigData),
Group(FilterConfigGroup),
Button(FilterConfigButton),
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum FilterConfigItemValue {
Track(f64),
Checkbox(bool),
Color(FilterConfigColorValue),
Select(i32),
File(String),
String(String),
Text(String),
Folder(String),
Data {
value: *mut std::ffi::c_void,
size: usize,
},
Group,
Button,
}
impl FilterConfigItem {
pub fn name(&self) -> &str {
match self {
FilterConfigItem::Track(item) => &item.name,
FilterConfigItem::Checkbox(item) => &item.name,
FilterConfigItem::Color(item) => &item.name,
FilterConfigItem::Select(item) => &item.name,
FilterConfigItem::File(item) => &item.name,
FilterConfigItem::String(item) => &item.name,
FilterConfigItem::Text(item) => &item.name,
FilterConfigItem::Folder(item) => &item.name,
FilterConfigItem::Data(item) => &item.name,
FilterConfigItem::Group(item) => item.name.as_deref().unwrap_or(""),
FilterConfigItem::Button(item) => &item.name,
}
}
pub(crate) fn to_raw(&self, leak_manager: &LeakManager) -> aviutl2_sys::filter2::FILTER_ITEM {
match self {
FilterConfigItem::Track(item) => {
let step: f64 = item.step.into();
aviutl2_sys::filter2::FILTER_ITEM {
track: aviutl2_sys::filter2::FILTER_ITEM_TRACK {
r#type: leak_manager.leak_as_wide_string("track"),
name: leak_manager.leak_as_wide_string(&item.name),
value: item.value,
s: *item.range.start(),
e: *item.range.end(),
step,
},
}
}
FilterConfigItem::Checkbox(item) => aviutl2_sys::filter2::FILTER_ITEM {
checkbox: aviutl2_sys::filter2::FILTER_ITEM_CHECKBOX {
r#type: leak_manager.leak_as_wide_string("check"),
name: leak_manager.leak_as_wide_string(&item.name),
value: item.value,
},
},
FilterConfigItem::Color(item) => aviutl2_sys::filter2::FILTER_ITEM {
color: aviutl2_sys::filter2::FILTER_ITEM_COLOR {
r#type: leak_manager.leak_as_wide_string("color"),
name: leak_manager.leak_as_wide_string(&item.name),
value: item.value.into(),
},
},
FilterConfigItem::Select(item) => {
let mut raw_items: Vec<aviutl2_sys::filter2::FILTER_ITEM_SELECT_ITEM> = item
.items
.iter()
.map(|i| aviutl2_sys::filter2::FILTER_ITEM_SELECT_ITEM {
name: leak_manager.leak_as_wide_string(&i.name),
value: i.value,
})
.collect();
raw_items.push(aviutl2_sys::filter2::FILTER_ITEM_SELECT_ITEM {
name: std::ptr::null(),
value: 0,
}); let raw_items_ptrs = leak_manager.leak_value_vec(raw_items);
aviutl2_sys::filter2::FILTER_ITEM {
select: aviutl2_sys::filter2::FILTER_ITEM_SELECT {
r#type: leak_manager.leak_as_wide_string("select"),
name: leak_manager.leak_as_wide_string(&item.name),
value: item.value,
items: raw_items_ptrs,
},
}
}
FilterConfigItem::File(item) => {
let raw_filters = crate::common::format_file_filters(&item.filters);
aviutl2_sys::filter2::FILTER_ITEM {
file: aviutl2_sys::filter2::FILTER_ITEM_FILE {
r#type: leak_manager.leak_as_wide_string("file"),
name: leak_manager.leak_as_wide_string(&item.name),
value: leak_manager.leak_as_wide_string(&item.value),
filefilter: leak_manager.leak_as_wide_string(&raw_filters),
},
}
}
FilterConfigItem::String(item) => aviutl2_sys::filter2::FILTER_ITEM {
string: aviutl2_sys::filter2::FILTER_ITEM_STRING {
r#type: leak_manager.leak_as_wide_string("string"),
name: leak_manager.leak_as_wide_string(&item.name),
value: leak_manager.leak_as_wide_string(&item.value),
},
},
FilterConfigItem::Text(item) => aviutl2_sys::filter2::FILTER_ITEM {
text: aviutl2_sys::filter2::FILTER_ITEM_TEXT {
r#type: leak_manager.leak_as_wide_string("text"),
name: leak_manager.leak_as_wide_string(&item.name),
value: leak_manager.leak_as_wide_string(&item.value),
},
},
FilterConfigItem::Folder(item) => aviutl2_sys::filter2::FILTER_ITEM {
folder: aviutl2_sys::filter2::FILTER_ITEM_FOLDER {
r#type: leak_manager.leak_as_wide_string("folder"),
name: leak_manager.leak_as_wide_string(&item.name),
value: leak_manager.leak_as_wide_string(&item.value),
},
},
FilterConfigItem::Data(item) => {
let mut data = aviutl2_sys::filter2::FILTER_ITEM_DATA {
r#type: leak_manager.leak_as_wide_string("data"),
name: leak_manager.leak_as_wide_string(&item.name),
value: std::ptr::null_mut(),
size: item.size as i32,
default_value: [MaybeUninit::new(0); 1024],
};
assert!(item.size <= 1024, "FilterConfigData size must be <= 1024");
unsafe {
std::ptr::copy_nonoverlapping(
item.default_value().as_ptr(),
data.default_value.as_mut_ptr() as *mut u8,
item.size,
);
}
aviutl2_sys::filter2::FILTER_ITEM { data }
}
FilterConfigItem::Group(item) => aviutl2_sys::filter2::FILTER_ITEM {
group: aviutl2_sys::filter2::FILTER_ITEM_GROUP {
r#type: leak_manager.leak_as_wide_string("group"),
name: leak_manager.leak_as_wide_string(item.name.as_deref().unwrap_or("")),
default_visible: item.opened,
},
},
FilterConfigItem::Button(item) => aviutl2_sys::filter2::FILTER_ITEM {
button: aviutl2_sys::filter2::FILTER_ITEM_BUTTON {
r#type: leak_manager.leak_as_wide_string("button"),
name: leak_manager.leak_as_wide_string(&item.name),
callback: item.callback,
},
},
}
}
pub(crate) unsafe fn get_value(
raw: *const aviutl2_sys::filter2::FILTER_ITEM,
) -> FilterConfigItemValue {
let item_type = unsafe {
crate::common::load_wide_string(
*(raw.cast::<aviutl2_sys::common::LPCWSTR>()),
)
};
match item_type.as_str() {
"track" => {
let raw_track = unsafe { &(*raw).track };
FilterConfigItemValue::Track(raw_track.value)
}
"check" => {
let raw_checkbox = unsafe { &(*raw).checkbox };
FilterConfigItemValue::Checkbox(raw_checkbox.value)
}
"color" => {
let raw_color = unsafe { &(*raw).color };
FilterConfigItemValue::Color(raw_color.value.into())
}
"select" => {
let raw_select = unsafe { &(*raw).select };
FilterConfigItemValue::Select(raw_select.value)
}
"file" => {
let raw_file = unsafe { &(*raw).file };
let value = unsafe { crate::common::load_wide_string(raw_file.value) };
FilterConfigItemValue::File(value)
}
"string" => {
let raw_string = unsafe { &(*raw).string };
let value = unsafe { crate::common::load_wide_string(raw_string.value) };
FilterConfigItemValue::String(value)
}
"text" => {
let raw_text = unsafe { &(*raw).text };
let value = unsafe { crate::common::load_wide_string(raw_text.value) };
FilterConfigItemValue::Text(value)
}
"folder" => {
let raw_folder = unsafe { &(*raw).folder };
let value = unsafe { crate::common::load_wide_string(raw_folder.value) };
FilterConfigItemValue::Folder(value)
}
"data" => {
let raw_size = unsafe { (*raw).data.size };
let raw_data = unsafe { (*raw).data.value };
let size =
usize::try_from(raw_size).expect("FILTER_ITEM_DATA size must not be negative");
assert!(
size <= 1024,
"FILTER_ITEM_DATA size must be 1024 bytes or less"
);
FilterConfigItemValue::Data {
value: raw_data,
size,
}
}
"group" => FilterConfigItemValue::Group,
"button" => FilterConfigItemValue::Button,
_ => panic!("Unknown filter config item type: {}", item_type),
}
}
pub(crate) unsafe fn should_apply_from_raw(
&self,
raw: *const aviutl2_sys::filter2::FILTER_ITEM,
) -> bool {
let value = unsafe { Self::get_value(raw) };
match (self, value) {
(FilterConfigItem::Track(item), FilterConfigItemValue::Track(v)) => item.value != v,
(FilterConfigItem::Checkbox(item), FilterConfigItemValue::Checkbox(v)) => {
item.value != v
}
(FilterConfigItem::Color(item), FilterConfigItemValue::Color(v)) => item.value != v,
(FilterConfigItem::Select(item), FilterConfigItemValue::Select(v)) => item.value != v,
(FilterConfigItem::File(item), FilterConfigItemValue::File(v)) => item.value != v,
(FilterConfigItem::String(item), FilterConfigItemValue::String(v)) => item.value != v,
(FilterConfigItem::Text(item), FilterConfigItemValue::Text(v)) => item.value != v,
(FilterConfigItem::Folder(item), FilterConfigItemValue::Folder(v)) => item.value != v,
(FilterConfigItem::Data(item), FilterConfigItemValue::Data { value, size }) => {
let size_changed = item.size != size;
let ptr_changed = match (item.value, NonNull::new(value)) {
(Some(old), Some(new)) => old != new,
(None, None) => false,
_ => true,
};
size_changed || ptr_changed
}
(FilterConfigItem::Group(_), FilterConfigItemValue::Group) => false,
(FilterConfigItem::Button(_), FilterConfigItemValue::Button) => false,
_ => {
panic!("Mismatched filter config item type");
}
}
}
pub(crate) unsafe fn apply_from_raw(&mut self, raw: *const aviutl2_sys::filter2::FILTER_ITEM) {
let value = unsafe { Self::get_value(raw) };
match (self, value) {
(FilterConfigItem::Track(item), FilterConfigItemValue::Track(v)) => {
item.value = v;
}
(FilterConfigItem::Checkbox(item), FilterConfigItemValue::Checkbox(v)) => {
item.value = v;
}
(FilterConfigItem::Color(item), FilterConfigItemValue::Color(v)) => {
item.value = v;
}
(FilterConfigItem::Select(item), FilterConfigItemValue::Select(v)) => {
item.value = v;
}
(FilterConfigItem::File(item), FilterConfigItemValue::File(v)) => {
item.value = v;
}
(FilterConfigItem::String(item), FilterConfigItemValue::String(v)) => {
item.value = v;
}
(FilterConfigItem::Text(item), FilterConfigItemValue::Text(v)) => {
item.value = v;
}
(FilterConfigItem::Folder(item), FilterConfigItemValue::Folder(v)) => {
item.value = v;
}
(FilterConfigItem::Data(item), FilterConfigItemValue::Data { value, size }) => {
item.size = size;
item.value = NonNull::new(value);
}
(FilterConfigItem::Group(_), FilterConfigItemValue::Group) => {
}
(FilterConfigItem::Button(_), FilterConfigItemValue::Button) => {
}
_ => {
panic!("Mismatched filter config item type");
}
}
}
}
#[derive(Debug, Clone)]
pub struct FilterConfigTrack {
pub name: String,
pub value: f64,
pub range: std::ops::RangeInclusive<f64>,
pub step: FilterConfigTrackStep,
}
#[derive(Clone, Copy)]
pub enum FilterConfigTrackStep {
One,
PointOne,
PointZeroOne,
PointZeroZeroOne,
}
impl TryFrom<f64> for FilterConfigTrackStep {
type Error = anyhow::Error;
fn try_from(value: f64) -> Result<Self, Self::Error> {
match value {
1.0 => Ok(FilterConfigTrackStep::One),
0.1 => Ok(FilterConfigTrackStep::PointOne),
0.01 => Ok(FilterConfigTrackStep::PointZeroOne),
0.001 => Ok(FilterConfigTrackStep::PointZeroZeroOne),
_ => Err(anyhow::anyhow!("Invalid step value: {}", value)),
}
}
}
impl From<FilterConfigTrackStep> for f64 {
fn from(value: FilterConfigTrackStep) -> Self {
match value {
FilterConfigTrackStep::One => 1.0,
FilterConfigTrackStep::PointOne => 0.1,
FilterConfigTrackStep::PointZeroOne => 0.01,
FilterConfigTrackStep::PointZeroZeroOne => 0.001,
}
}
}
impl std::fmt::Debug for FilterConfigTrackStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value: f64 = (*self).into();
f.debug_tuple("FilterConfigTrackStep")
.field(&value)
.finish()
}
}
#[derive(Debug, Clone)]
pub struct FilterConfigCheckbox {
pub name: String,
pub value: bool,
}
#[derive(Debug, Clone)]
pub struct FilterConfigColor {
pub name: String,
pub value: FilterConfigColorValue,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FilterConfigColorValue(pub u32);
impl FilterConfigColorValue {
pub fn to_rgb(&self) -> (u8, u8, u8) {
let r = ((self.0 >> 16) & 0xFF) as u8;
let g = ((self.0 >> 8) & 0xFF) as u8;
let b = (self.0 & 0xFF) as u8;
(r, g, b)
}
pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
let value = (r as u32) << 16 | (g as u32) << 8 | (b as u32);
FilterConfigColorValue(value)
}
}
impl From<u32> for FilterConfigColorValue {
fn from(value: u32) -> Self {
FilterConfigColorValue(value)
}
}
impl From<FilterConfigColorValue> for u32 {
fn from(value: FilterConfigColorValue) -> Self {
value.0
}
}
impl From<aviutl2_sys::filter2::FILTER_ITEM_COLOR_VALUE> for FilterConfigColorValue {
fn from(value: aviutl2_sys::filter2::FILTER_ITEM_COLOR_VALUE) -> Self {
unsafe { FilterConfigColorValue(value.code) }
}
}
impl From<FilterConfigColorValue> for aviutl2_sys::filter2::FILTER_ITEM_COLOR_VALUE {
fn from(value: FilterConfigColorValue) -> Self {
aviutl2_sys::filter2::FILTER_ITEM_COLOR_VALUE { code: value.0 }
}
}
impl std::fmt::Display for FilterConfigColorValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (r, g, b) = self.to_rgb();
write!(f, "#{:02X}{:02X}{:02X}", r, g, b)
}
}
impl std::fmt::LowerHex for FilterConfigColorValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:06x}", self.0 & 0xFFFFFF)
}
}
impl std::fmt::UpperHex for FilterConfigColorValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:06X}", self.0 & 0xFFFFFF)
}
}
#[derive(Debug, Clone)]
pub struct FilterConfigSelect {
pub name: String,
pub value: i32,
pub items: Vec<FilterConfigSelectItem>,
}
#[derive(Debug, Clone)]
pub struct FilterConfigSelectItem {
pub name: String,
pub value: i32,
}
pub trait FilterConfigSelectItems {
fn to_select_items() -> Vec<FilterConfigSelectItem>;
fn from_select_item_value(item: i32) -> Self;
fn to_select_item_value(&self) -> i32;
}
#[doc(inline)]
pub use aviutl2_macros::FilterConfigSelectItems;
#[derive(Debug, Clone)]
pub struct FilterConfigFile {
pub name: String,
pub value: String,
pub filters: Vec<crate::common::FileFilter>,
}
#[derive(Debug, Clone)]
pub struct FilterConfigButton {
pub name: String,
pub callback: extern "C" fn(*mut EDIT_SECTION),
}
#[derive(Debug, Clone)]
pub struct FilterConfigString {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone)]
pub struct FilterConfigText {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone)]
pub struct FilterConfigFolder {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone)]
pub struct ErasedFilterConfigData {
pub name: String,
pub size: usize,
pub value: Option<NonNull<std::ffi::c_void>>,
default_value: [u8; 1024],
}
impl ErasedFilterConfigData {
pub fn new<T: Copy + Default + 'static>(name: String) -> Self {
Self::with_default_value(name, T::default())
}
pub fn with_default_value<T: Copy + 'static>(name: String, default_value: T) -> Self {
assert!(
std::mem::size_of::<T>() <= 1024,
"FilterConfigData<T> size must be <= 1024 bytes"
);
let size = std::mem::size_of::<T>();
let mut default_value_bytes = [0u8; 1024];
let default_value_ptr = (&raw const default_value).cast::<u8>();
default_value_bytes[..size]
.copy_from_slice(unsafe { std::slice::from_raw_parts(default_value_ptr, size) });
ErasedFilterConfigData {
name,
size,
value: None,
default_value: default_value_bytes,
}
}
pub fn default_value(&self) -> &[u8] {
&self.default_value[..self.size]
}
pub unsafe fn into_typed<T: Copy + 'static>(self) -> FilterConfigData<T> {
let expected_size = std::mem::size_of::<T>();
assert_eq!(
self.size, expected_size,
"Size mismatch when converting ErasedFilterConfigData to FilterConfigData<T>"
);
let value = self
.value
.map(|v| NonNull::new(v.as_ptr().cast::<T>()).unwrap());
let default_value_ptr = self.default_value.as_ptr().cast::<T>();
let default_value = unsafe { *default_value_ptr };
FilterConfigData {
name: self.name,
value,
default_value,
}
}
}
#[derive(Debug, Clone)]
pub struct FilterConfigData<T: Copy + 'static> {
pub name: String,
pub value: Option<NonNull<T>>,
pub default_value: T,
}
impl<T: Copy + 'static> FilterConfigData<T> {
pub fn erase_type(&self) -> ErasedFilterConfigData {
assert!(
std::mem::size_of::<T>() <= 1024,
"FilterConfigData<T> size must be <= 1024 bytes"
);
let size = std::mem::size_of::<T>();
let mut default_value = [0u8; 1024];
let default_value_ptr = (&raw const self.default_value).cast::<u8>();
default_value[..size]
.copy_from_slice(unsafe { std::slice::from_raw_parts(default_value_ptr, size) });
ErasedFilterConfigData {
name: self.name.clone(),
size,
value: self
.value
.map(|v| NonNull::new(v.as_ptr().cast::<c_void>()).unwrap()),
default_value,
}
}
}
impl<T: Copy + 'static> From<FilterConfigData<T>> for ErasedFilterConfigData {
fn from(value: FilterConfigData<T>) -> Self {
value.erase_type()
}
}
#[derive(Debug, Clone)]
pub struct FilterConfigGroup {
pub name: Option<String>,
pub opened: bool,
}
impl FilterConfigGroup {
pub fn start(name: String) -> Self {
Self::start_with_opened(name, true)
}
pub fn start_with_opened(name: String, opened: bool) -> Self {
FilterConfigGroup {
name: Some(name),
opened,
}
}
pub fn end() -> Self {
FilterConfigGroup {
name: None,
opened: false,
}
}
}