diff --git a/src/fe_config.cpp b/src/fe_config.cpp index f85cfd10..37a9804b 100644 --- a/src/fe_config.cpp +++ b/src/fe_config.cpp @@ -1939,6 +1939,8 @@ bool FeScriptConfigMenu::on_option_select( bool FeScriptConfigMenu::save_helper( FeConfigContext &ctx, int first_idx ) { + apply_presets_if_changed( ctx, first_idx ); + m_configurable->clear_params(); if ( m_per_display ) @@ -1970,6 +1972,27 @@ bool FeScriptConfigMenu::save_helper( FeConfigContext &ctx, int first_idx ) return true; } +void FeScriptConfigMenu::apply_presets_if_changed( FeConfigContext &ctx, int first_idx ) +{ + FeVM::script_apply_preset_values( ctx, *m_configurable, m_file_path, m_file_name, first_idx ); +} + +void FeScriptConfigMenu::apply_preset_to_context( FeConfigContext &ctx, int preset_index ) +{ + // Use force_apply=true so preset is applied even if it matches the saved value + FeVM::script_apply_preset_values( ctx, *m_configurable, m_file_path, m_file_name, 0, true ); +} + +void FeScriptConfigMenu::handle_preset_override( FeConfigContext &ctx, int changed_option_index ) +{ + FeVM::script_handle_preset_override( ctx, *m_configurable, m_file_path, m_file_name, changed_option_index ); +} + +void FeScriptConfigMenu::update_preset_controlled_options( FeConfigContext &ctx ) +{ + FeVM::script_update_preset_controlled_options( ctx, *m_configurable, m_file_path, m_file_name ); +} + FePluginEditMenu::FePluginEditMenu() : m_plugin( NULL ) { diff --git a/src/fe_config.hpp b/src/fe_config.hpp index 3502af77..2d0ce5a1 100644 --- a/src/fe_config.hpp +++ b/src/fe_config.hpp @@ -62,6 +62,7 @@ class FeMenuOpt int type; // see Opt namespace for values bool trigger_reload = false; // this option will trigger a ui reload on change bool trigger_colour = false; // special case for menu colour options + bool preset_controlled = false; // this option is controlled by a preset std::string setting; // the name of the setting std::string help_msg; // the help message for this option @@ -205,6 +206,12 @@ class FeBaseConfigMenu // virtual bool save( FeConfigContext &ctx ); + // Apply preset values to context immediately when preset selection changes + virtual void apply_preset_to_context( FeConfigContext &ctx, int preset_index ) {} + + // Handle when a non-preset option changes - set preset field to "Custom" if applicable + virtual void handle_preset_override( FeConfigContext &ctx, int changed_option_index ) {} + // When true will cause display_config_dialog to save and exit on every change // Used to create "live" menus, such as Layout Options // main.cpp must then re-display the menu until user explicitly exits @@ -228,6 +235,9 @@ class FeScriptConfigMenu : public FeBaseConfigMenu FeConfigContext &ctx, FeBaseConfigMenu *& submenu ); bool save_helper( FeConfigContext &ctx, int first_idx=0 ); + void apply_presets_if_changed( FeConfigContext &ctx, int first_idx ); + void apply_preset_to_context( FeConfigContext &ctx, int preset_index ) override; + void handle_preset_override( FeConfigContext &ctx, int changed_option_index ) override; FeSettings::FePresentState m_state; int m_script_id; @@ -235,6 +245,9 @@ class FeScriptConfigMenu : public FeBaseConfigMenu FeScriptConfigurable *m_per_display; std::string m_file_path; std::string m_file_name; + +public: + void update_preset_controlled_options( FeConfigContext &ctx ); }; class FeLayoutEditMenu : public FeScriptConfigMenu diff --git a/src/fe_listbox.cpp b/src/fe_listbox.cpp index 54ea6350..15e8bdfa 100644 --- a/src/fe_listbox.cpp +++ b/src/fe_listbox.cpp @@ -931,6 +931,20 @@ void FeListBox::set_sela(int a) setSelColor( c ); } +void FeListBox::set_row_alpha( int index, int alpha ) +{ + // Convert absolute list index to visible row index + int visible_row = index - m_list_start_offset; + + // Only apply if the row is currently visible + if ( visible_row >= 0 && visible_row < (int)m_texts.size() ) + { + sf::Color c = m_texts[visible_row].getColor(); + c.a = alpha; + m_texts[visible_row].setColor( c ); + } +} + void FeListBox::set_sel_rgb(int r, int g, int b ) { sf::Color c = m_selColour; diff --git a/src/fe_listbox.hpp b/src/fe_listbox.hpp index 10aa51d8..839ebd9e 100644 --- a/src/fe_listbox.hpp +++ b/src/fe_listbox.hpp @@ -160,6 +160,7 @@ class FeListBox : public FeBasePresentable, public sf::Drawable void set_selb(int b); void set_sela(int a); void set_sel_rgb( int, int, int ); + void set_row_alpha( int index, int alpha ); int get_selbgr(); int get_selbgg(); int get_selbgb(); diff --git a/src/fe_overlay.cpp b/src/fe_overlay.cpp index c75da6bd..1fec8e1d 100644 --- a/src/fe_overlay.cpp +++ b/src/fe_overlay.cpp @@ -1527,6 +1527,10 @@ int FeOverlay::display_config_dialog( ctx.curr_sel = 0; m->last_sel = ctx.curr_sel; + FeScriptConfigMenu *script_menu = dynamic_cast(m); + if ( script_menu ) + script_menu->update_preset_controlled_options( ctx ); + sdialog.setCustomText( ctx.curr_sel, ctx.left_list ); vdialog.setCustomText( ctx.curr_sel, ctx.right_list ); @@ -1555,6 +1559,16 @@ int FeOverlay::display_config_dialog( text_index( vdialog, vindex, ctx.right_list, -1 ); layout_focus( sdialog, vdialog, ( ctx.curr_opt().type == Opt::INFO ) ? LayoutFocus::Disabled : LayoutFocus::Select ); + // Apply dimming to preset-controlled options + for ( unsigned int i = 0; i < ctx.opt_list.size(); i++ ) + { + if ( ctx.opt_list[i].preset_controlled ) + { + sdialog.set_row_alpha( i, m_disable_alpha ); + vdialog.set_row_alpha( i, m_disable_alpha ); + } + } + while ( text_index( sdialog, sindex, ctx.left_list, is_preview ? c.sel : -1 ) && event_loop( c ) == false ) { m->last_sel = ctx.curr_sel; @@ -1568,6 +1582,16 @@ int FeOverlay::display_config_dialog( sdialog.setCustomText( ctx.curr_sel, ctx.left_list ); vdialog.setCustomText( ctx.curr_sel, ctx.right_list ); + + // Reapply dimming after navigation + for ( unsigned int i = 0; i < ctx.opt_list.size(); i++ ) + { + if ( ctx.opt_list[i].preset_controlled ) + { + sdialog.set_row_alpha( i, m_disable_alpha ); + vdialog.set_row_alpha( i, m_disable_alpha ); + } + } } text_index( sdialog, sindex, ctx.left_list, -1 ); @@ -1714,6 +1738,31 @@ int FeOverlay::display_config_dialog( ctx.save_req = true; ctx.curr_opt().set_value( new_value ); ctx.right_list[ctx.curr_sel] = ctx.curr_opt().get_value(); + + // Note: Opaque values are: 1=is_input, 2=is_function, 3=is_preset + if ( ctx.opt_list[ctx.curr_sel].opaque == 3 ) + { + m->apply_preset_to_context( ctx, ctx.curr_sel ); + + for ( unsigned int i = 0; i < ctx.opt_list.size(); i++ ) + ctx.right_list[i] = ctx.opt_list[i].get_value(); + + FeScriptConfigMenu *script_menu = dynamic_cast(m); + if ( script_menu ) + script_menu->update_preset_controlled_options( ctx ); + } + else + { + m->handle_preset_override( ctx, ctx.curr_sel ); + + for ( unsigned int i = 0; i < ctx.opt_list.size(); i++ ) + if ( ctx.opt_list[i].opaque == 3 ) + ctx.right_list[i] = ctx.opt_list[i].get_value(); + + FeScriptConfigMenu *script_menu = dynamic_cast(m); + if ( script_menu ) + script_menu->update_preset_controlled_options( ctx ); + } } else if ( refresh_colour ) { @@ -1742,6 +1791,12 @@ int FeOverlay::display_config_dialog( std::string d_str = utf32_to_utf8( str ); ctx.curr_opt().set_value( d_str ); ctx.right_list[ctx.curr_sel] = d_str; + + m->handle_preset_override( ctx, ctx.curr_sel ); + + for ( unsigned int i = 0; i < ctx.opt_list.size(); i++ ) + if ( ctx.opt_list[i].opaque == 3 ) + ctx.right_list[i] = ctx.opt_list[i].get_value(); } } @@ -1778,6 +1833,14 @@ int FeOverlay::display_config_dialog( ctx.save_req = true; + m->handle_preset_override( ctx, ctx.curr_sel ); + + for ( unsigned int i = 0; i < ctx.opt_list.size(); i++ ) + if ( ctx.opt_list[i].opaque == 3 ) + ctx.right_list[i] = ctx.opt_list[i].get_value(); + + vdialog.setCustomText( ctx.curr_sel, ctx.right_list ); + if ( m->exit_on_change ) { if ( m->save( ctx ) ) parent_setting_changed = true; diff --git a/src/fe_vm.cpp b/src/fe_vm.cpp index c9a8542d..ebc2c9f9 100644 --- a/src/fe_vm.cpp +++ b/src/fe_vm.cpp @@ -2203,6 +2203,295 @@ void FeVM::script_get_config_options( } } +void FeVM::script_apply_preset_values( + FeConfigContext &ctx, + FeScriptConfigurable &configurable, + const std::string &script_path, + const std::string &script_file, + int first_idx, + bool force_apply ) +{ + if ( script_path.empty() || script_file.empty() ) + return; + + FeConfigVM config_vm( configurable, script_path, script_file ); + HSQUIRRELVM vm = config_vm.get_vm(); + + Sqrat::Object uConfig = Sqrat::RootTable().GetSlot( _SC("UserConfig") ); + if ( uConfig.IsNull() ) + return; + + // Find preset fields and check if any have changed + for ( unsigned int i = first_idx; i < ctx.opt_list.size(); i++ ) + { + std::string key = ctx.opt_list[i].opaque_str; + + // Strip '%' if per_display option + if ( !key.empty() && key[0] == '%' ) + key = key.substr( 1 ); + + bool is_preset = false; + HSQOBJECT obj = uConfig.GetObject(); + fe_get_attribute_bool( vm, obj, key, "is_preset", is_preset ); + + if ( !is_preset ) + continue; + + std::string new_preset_value = ctx.opt_list[i].get_value(); + + if ( new_preset_value == _( "Custom" ) ) + continue; + + std::string old_preset_value; + if ( !configurable.get_param( key, old_preset_value ) ) + fe_get_object_string( vm, uConfig.GetSlot( key.c_str() ), old_preset_value ); + + if ( !force_apply && ( new_preset_value == old_preset_value ) ) + continue; + + sq_pushobject(vm, obj); + sq_pushstring(vm, scsqchar( key ), key.size() ); + + if ( SQ_SUCCEEDED( sq_getattributes(vm, -2) ) ) + { + HSQOBJECT attr_obj; + sq_resetobject( &attr_obj ); + sq_getstackobj( vm, -1, &attr_obj ); + + Sqrat::Object attr_table( attr_obj, vm ); + sq_pop( vm, 2 ); + + if ( !attr_table.IsNull() ) + { + Sqrat::Object presets_table = attr_table.GetSlot( _SC("presets") ); + if ( !presets_table.IsNull() ) + { + Sqrat::Object preset_values = presets_table.GetSlot( new_preset_value.c_str() ); + if ( !preset_values.IsNull() ) + { + // Apply all values from this preset to ctx.opt_list + Sqrat::Object::iterator preset_it; + while ( preset_values.Next( preset_it ) ) + { + std::string preset_key, preset_value; + fe_get_object_string( vm, preset_it.getKey(), preset_key ); + fe_get_object_string( vm, preset_it.getValue(), preset_value ); + + // Find the corresponding option in ctx.opt_list and update it + for ( unsigned int j = first_idx; j < ctx.opt_list.size(); j++ ) + { + std::string opt_key = ctx.opt_list[j].opaque_str; + + // Strip '%' if per_display option + if ( !opt_key.empty() && opt_key[0] == '%' ) + opt_key = opt_key.substr( 1 ); + + if ( opt_key == preset_key ) + { + ctx.opt_list[j].set_value( preset_value ); + break; + } + } + } + } + } + } + } + else + { + sq_pop( vm, 1 ); + } + } +} + +void FeVM::script_handle_preset_override( + FeConfigContext &ctx, + FeScriptConfigurable &configurable, + const std::string &script_path, + const std::string &script_file, + int changed_option_index ) +{ + if ( script_path.empty() || script_file.empty() ) + return; + + FeConfigVM config_vm( configurable, script_path, script_file ); + HSQUIRRELVM vm = config_vm.get_vm(); + + Sqrat::Object uConfig = Sqrat::RootTable().GetSlot( _SC("UserConfig") ); + if ( uConfig.IsNull() ) + return; + + std::string changed_key = ctx.opt_list[changed_option_index].opaque_str; + + // Strip '%' if per_display option + if ( !changed_key.empty() && changed_key[0] == '%' ) + changed_key = changed_key.substr( 1 ); + + // Find all preset fields and check if the changed option is controlled by any of them + for ( unsigned int i = 0; i < ctx.opt_list.size(); i++ ) + { + std::string key = ctx.opt_list[i].opaque_str; + + // Strip '%' if per_display option + if ( !key.empty() && key[0] == '%' ) + key = key.substr( 1 ); + + bool is_preset = false; + HSQOBJECT obj = uConfig.GetObject(); + fe_get_attribute_bool( vm, obj, key, "is_preset", is_preset ); + + if ( !is_preset ) + continue; + + // Get the presets table to check if changed_key is controlled by this preset + sq_pushobject(vm, obj); + sq_pushstring(vm, scsqchar( key ), key.size() ); + + if ( SQ_SUCCEEDED( sq_getattributes(vm, -2) ) ) + { + HSQOBJECT attr_obj; + sq_resetobject( &attr_obj ); + sq_getstackobj( vm, -1, &attr_obj ); + + Sqrat::Object attr_table( attr_obj, vm ); + sq_pop( vm, 2 ); + + if ( !attr_table.IsNull() ) + { + Sqrat::Object presets_table = attr_table.GetSlot( _SC("presets") ); + if ( !presets_table.IsNull() ) + { + // Check if any preset in the table controls the changed option + bool found = false; + Sqrat::Object::iterator preset_it; + while ( presets_table.Next( preset_it ) ) + { + Sqrat::Object preset_values = preset_it.getValue(); + if ( !preset_values.IsNull() ) + { + Sqrat::Object controlled_value = preset_values.GetSlot( changed_key.c_str() ); + if ( !controlled_value.IsNull() ) + { + found = true; + break; + } + } + } + + if ( found ) + { + std::string current_preset = ctx.opt_list[i].get_value(); + if ( current_preset != _( "Custom" ) ) + { + ctx.opt_list[i].set_value( _( "Custom" ) ); + ctx.save_req = true; + } + + return; + } + } + } + } + else + { + sq_pop( vm, 1 ); + } + } +} + +void FeVM::script_update_preset_controlled_options( + FeConfigContext &ctx, + FeScriptConfigurable &configurable, + const std::string &script_path, + const std::string &script_file ) +{ + for ( unsigned int i = 0; i < ctx.opt_list.size(); i++ ) + ctx.opt_list[i].preset_controlled = false; + + if ( script_path.empty() || script_file.empty() ) + return; + + FeConfigVM config_vm( configurable, script_path, script_file ); + HSQUIRRELVM vm = config_vm.get_vm(); + + Sqrat::Object uConfig = Sqrat::RootTable().GetSlot( _SC("UserConfig") ); + if ( uConfig.IsNull() ) + return; + + for ( unsigned int i = 0; i < ctx.opt_list.size(); i++ ) + { + std::string key = ctx.opt_list[i].opaque_str; + + // Strip '%' if per_display option + if ( !key.empty() && key[0] == '%' ) + key = key.substr( 1 ); + + bool is_preset = false; + HSQOBJECT obj = uConfig.GetObject(); + fe_get_attribute_bool( vm, obj, key, "is_preset", is_preset ); + + if ( !is_preset ) + continue; + + std::string current_preset = ctx.opt_list[i].get_value(); + + if ( current_preset == _( "Custom" ) || current_preset.empty() ) + continue; + + // Get the presets table and set the flag for all controlled options + sq_pushobject(vm, obj); + sq_pushstring(vm, scsqchar( key ), key.size() ); + + if ( SQ_SUCCEEDED( sq_getattributes(vm, -2) ) ) + { + HSQOBJECT attr_obj; + sq_resetobject( &attr_obj ); + sq_getstackobj( vm, -1, &attr_obj ); + + Sqrat::Object attr_table( attr_obj, vm ); + sq_pop( vm, 2 ); + + if ( !attr_table.IsNull() ) + { + Sqrat::Object presets_table = attr_table.GetSlot( _SC("presets") ); + if ( !presets_table.IsNull() ) + { + // Get the current preset configuration + Sqrat::Object preset_values = presets_table.GetSlot( current_preset.c_str() ); + if ( !preset_values.IsNull() ) + { + Sqrat::Object::iterator preset_it; + while ( preset_values.Next( preset_it ) ) + { + std::string controlled_key; + fe_get_object_string( vm, preset_it.getKey(), controlled_key ); + + for ( unsigned int j = 0; j < ctx.opt_list.size(); j++ ) + { + std::string opt_key = ctx.opt_list[j].opaque_str; + + // Strip '%' if per_display option + if ( !opt_key.empty() && opt_key[0] == '%' ) + opt_key = opt_key.substr( 1 ); + + if ( opt_key == controlled_key ) + { + ctx.opt_list[j].preset_controlled = true; + break; + } + } + } + } + } + } + } + else + { + sq_pop( vm, 1 ); + } + } +} + void FeVM::script_get_config_options( FeConfigContext &ctx, std::string &gen_help, @@ -2230,7 +2519,7 @@ void FeVM::script_get_config_options( HSQOBJECT obj = uConfig.GetObject(); std::string o_value = "", o_label = ""; std::string key = "", value = "", label = "", help = "", options = ""; - bool is_input = false, is_func = false, is_info = false, per_display = false; + bool is_input = false, is_func = false, is_info = false, per_display = false, is_preset = false; int order = -1; // use the default value from the script if a value has not already been configured @@ -2245,6 +2534,7 @@ void FeVM::script_get_config_options( fe_get_attribute_bool( vm, obj, key, "is_function", is_func); fe_get_attribute_bool( vm, obj, key, "is_info", is_info); fe_get_attribute_bool( vm, obj, key, "per_display", per_display ); + fe_get_attribute_bool( vm, obj, key, "is_preset", is_preset ); if ( !configurable.get_param( key, value ) ) value = o_value; label = !o_label.empty() ? o_label : key; @@ -2254,6 +2544,27 @@ void FeVM::script_get_config_options( { std::vector options_list; size_t pos=0; + + // For preset fields, ensure "Custom" is always the first option + if ( is_preset ) + { + bool has_custom = false; + std::string first_option; + size_t check_pos = 0; + token_helper( options, check_pos, first_option, "," ); + if ( first_option == _( "Custom" ) ) + has_custom = true; + + if ( !has_custom ) + options_list.push_back( _( "Custom" ) ); + + // Reset position to parse all options + pos = 0; + + if ( value.empty() ) + value = _( "Custom" ); + } + do { std::string temp; @@ -2301,6 +2612,10 @@ void FeVM::script_get_config_options( (*itx).second.opaque_str = "%"; (*itx).second.opaque_str += temp; } + + // Note: Opaque values are: 1=is_input, 2=is_function, 3=is_preset + if ( is_preset ) + (*itx).second.opaque = 3; } for ( std::multimap::iterator itr = my_opts.begin(); itr != my_opts.end(); ++itr ) @@ -3295,6 +3610,14 @@ Sqrat::Table FeVM::cb_get_config() if ( !fev->m_script_cfg || !fev->m_script_cfg->get_param( key, value ) ) { fe_get_object_string( vm, it.getValue(), value ); + + if ( value.empty() ) + { + bool is_preset = false; + fe_get_attribute_bool( vm, uConfig.GetObject(), key, "is_preset", is_preset ); + if ( is_preset ) + value = _( "Custom" ); + } } retval.SetValue( key.c_str(), value.c_str() ); diff --git a/src/fe_vm.hpp b/src/fe_vm.hpp index 4bc9859a..02e5e208 100644 --- a/src/fe_vm.hpp +++ b/src/fe_vm.hpp @@ -188,6 +188,27 @@ class FeVM : public FePresent const std::string &script_path, const std::string &script_file ); + static void script_apply_preset_values( + FeConfigContext &ctx, + FeScriptConfigurable &configurable, + const std::string &script_path, + const std::string &script_file, + int first_idx, + bool force_apply = false ); + + static void script_handle_preset_override( + FeConfigContext &ctx, + FeScriptConfigurable &configurable, + const std::string &script_path, + const std::string &script_file, + int changed_option_index ); + + static void script_update_preset_controlled_options( + FeConfigContext &ctx, + FeScriptConfigurable &configurable, + const std::string &script_path, + const std::string &script_file ); + static void script_run_config_function( FeScriptConfigurable &configurable, const std::string &script_path,