diff --git a/examples/x-touch.lua b/examples/x-touch.lua index 7da3d54..4675d85 100644 --- a/examples/x-touch.lua +++ b/examples/x-touch.lua @@ -59,150 +59,266 @@ local du = require "lib/dtutils" du.check_min_api_version("9.2.0", "x-touch") local gettext = dt.gettext.gettext - -local function _(msgid) - return gettext(msgid) +local function _(msgid) + return gettext(msgid) end --- return data structure for script_manager +-- Constants +local NUM_KNOBS = 8 +local NAN = 0/0 -local script_data = {} - -script_data.metadata = { - name = _("x-touch"), - purpose = _("example of how to control an x-touch midi device"), - author = "Diederik ter Rahe", - help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/x-touch" +-- Configuration tables +local MASK_PROPERTIES = { + "opacity", "size", "feather", "hardness", + "rotation", "curvature", "compression" } -script_data.destroy = nil -- function to destory the script -script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet -script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again -script_data.show = nil -- only required for libs since the destroy_method only hides them +local COLOR_ELEMENTS = { + colorzones = { + "red", "orange", "yellow", "green", + "aqua", "blue", "purple", "magenta" + }, + colorequal = { + "red", "orange", "yellow", "green", + "cyan", "blue", "lavender", "magenta" + } +} --- set up 8 mimic sliders with the same function -for k = 1,8 do - dt.gui.mimic("slider", "knob ".. k, - function(action, element, effect, size) - -- take the number from the mimic name - local k = tonumber(action:sub(-1)) +local MODULE_CONFIGS = { + sigmoid = { + "red attenuation", "red rotation", + "green attenuation", "green rotation", + "blue attenuation", "blue rotation", + "recover purity" + }, + primaries = { + "red hue", "red purity", "green hue", "green purity", + "blue hue", "blue purity", "tint hue", "tint purity" + }, + agx = { + "white relative exposure", + "black relative exposure", + "curve/pivot input shift", + "curve/pivot target output", + "curve/contrast", + "curve/toe power", + "curve/shoulder power", + "curve/curve y gamma" + } +} - -- only operate in darkroom; return NAN otherwise - if dt.gui.current_view() ~= dt.gui.views.darkroom then - return 0/0 - end +local DEFAULT_MAPPINGS = { + "iop/exposure/exposure", + "iop/filmicrgb/white relative exposure", + "iop/filmicrgb/black relative exposure", + "iop/filmicrgb/contrast", + "iop/crop/left", + "iop/crop/right", + "iop/crop/top", + "iop/crop/bottom" +} - local maskval = 0/0 - if k < 8 then - -- first try if the mask slider at that position is active - local s = { "opacity", - "size", - "feather", - "hardness", - "rotation", - "curvature", - "compression" } - maskval = dt.gui.action("lib/masks/properties/" .. s[k], - element, effect, size) - end - -- if a value different from NAN is returned, the slider was active - if maskval == maskval then - return maskval - - -- try if colorzones module is focused; if so select element of graph - elseif dt.gui.action("iop/colorzones", "focus") ~= 0 then - which = "iop/colorzones/graph" - local e = { "red", - "orange", - "yellow", - "green", - "aqua", - "blue", - "purple", - "magenta" } - element = e[k] - - -- try if colorequalizer module is focused; if so select element of graph - elseif dt.gui.action("iop/colorequal", "focus") ~= 0 then - local e = { "red", - "orange", - "yellow", - "green", - "cyan", - "blue", - "lavender", - "magenta" } - which = "iop/colorequal/graph" - element = e[k] - - -- if the sigmoid rgb primaries is focused, - -- check sliders - elseif dt.gui.action("iop/sigmoid", "focus") ~= 0 and k <8 then - local e = { "red attenuation", "red rotation", "green attenuation", "green rotation", "blue attenuation", "blue rotation", "recover purity" } - which = "iop/sigmoid/primaries/"..e[k] - - -- if the rgb primaries is focused, - -- check sliders - elseif dt.gui.action("iop/primaries", "focus") ~= 0 and k >=1 then - local e = { "red hue", "red purity", "green hue", "green purity", "blue hue", "blue purity", "tint hue", "tint purity" } - which = "iop/primaries/" ..e[k] - - -- if the tone equalizer is focused, - -- select one of the sliders in the "simple" tab - elseif dt.gui.action("iop/toneequal", "focus") ~= 0 then - which ="iop/toneequal/simple/"..(k-9).." EV" - - -- if color calibration is focused, - -- the last 4 knobs are sent there - elseif dt.gui.action("iop/channelmixerrgb", "focus") ~= 0 - and k >= 5 then - -- knob 5 selects the active tab; pressing it resets to CAT - if k == 5 then - which = "iop/channelmixerrgb/page" - element = "CAT" - -- since the tab action is not a slider, - -- the effects need to be translated - if effect == "up" then effect = "next" - elseif effect == "down" then effect = "previous" - else effect = "activate" - end - else - -- knobs 6, 7 and 8 are for the three color sliders on each tab - which = "iop/focus/sliders" - local e = { "1st", - "2nd", - "3rd" } - element = e[k - 5] - end - - -- the 4th knob is contrast; - -- either colorbalance if it is focused, or filmic - elseif dt.gui.action("iop/colorbalancergb", "focus") ~= 0 - and k == 4 then - which = "iop/colorbalancergb/contrast" - - -- in all other cases use a default selection - else - local s = { "iop/exposure/exposure", - "iop/filmicrgb/white relative exposure", - "iop/filmicrgb/black relative exposure", - "iop/filmicrgb/contrast", - "iop/crop/left", - "iop/crop/right", - "iop/crop/top", - "iop/crop/bottom" } - which = s[k] - end - - -- now pass the element/effect/size to the selected slider - return dt.gui.action(which, element, effect, size) - end) +-- Helper functions +local function is_in_darkroom() + return dt.gui.current_view() == dt.gui.views.darkroom end -local function destroy() - -- nothing to destroy +local function is_valid(value) + return value == value -- NaN check end -script_data.destroy = destroy +local function is_module_focused(module_name) + return dt.gui.action("iop/" .. module_name, "focus") ~= 0 +end + +local function translate_tab_effect(effect) + if effect == "up" then + return "next" + elseif effect == "down" then + return "previous" + else + return "activate" + end +end + +-- Handler functions for different module types +local function handle_mask_properties(knob_idx, element, effect, size) + if knob_idx >= NUM_KNOBS then + return NAN + end + + local property = MASK_PROPERTIES[knob_idx] + local value = dt.gui.action("lib/masks/properties/" .. property, + element, effect, size) + return value +end + +local function handle_color_module(module_name, knob_idx, element, effect, size) + local elements = COLOR_ELEMENTS[module_name] + if not elements then + return nil + end + + return dt.gui.action("iop/" .. module_name .. "/graph", + elements[knob_idx], effect, size) +end + +local function handle_sigmoid(knob_idx, element, effect, size) + if knob_idx >= NUM_KNOBS then + return nil + end + + local slider = MODULE_CONFIGS.sigmoid[knob_idx] + return dt.gui.action("iop/sigmoid/primaries/" .. slider, + element, effect, size) +end + +local function handle_primaries(knob_idx, element, effect, size) + local slider = MODULE_CONFIGS.primaries[knob_idx] + return dt.gui.action("iop/primaries/" .. slider, + element, effect, size) +end + +local function handle_agx(knob_idx, element, effect, size) + local slider = MODULE_CONFIGS.agx[knob_idx] + return dt.gui.action("iop/agx/" .. slider, + element, effect, size) +end + +local function handle_tone_equalizer(knob_idx, element, effect, size) + return dt.gui.action("iop/toneequal/simple/" .. (knob_idx - 9) .. " EV", + element, effect, size) +end + +local function handle_channel_mixer(knob_idx, element, effect, size) + if knob_idx < 5 then + return nil + end + + if knob_idx == 5 then + -- Tab selection + return dt.gui.action("iop/channelmixerrgb/page", + "CAT", + translate_tab_effect(effect), + size) + else + -- Color sliders + local positions = {"1st", "2nd", "3rd"} + return dt.gui.action("iop/focus/sliders", + positions[knob_idx - 5], + effect, size) + end +end + +local function handle_color_balance_contrast(knob_idx, element, effect, size) + if knob_idx ~= 4 then + return nil + end + return dt.gui.action("iop/colorbalancergb/contrast", + element, effect, size) +end + +local function handle_default_mapping(knob_idx, element, effect, size) + local mapping = DEFAULT_MAPPINGS[knob_idx] + return dt.gui.action(mapping, element, effect, size) +end + +-- Main knob handler +local function handle_knob_action(knob_idx, element, effect, size) + if not is_in_darkroom() then + return NAN + end + + -- Try mask properties first + local mask_value = handle_mask_properties(knob_idx, element, effect, size) + if is_valid(mask_value) then + return mask_value + end + + -- Try color modules + if is_module_focused("colorzones") then + return handle_color_module("colorzones", knob_idx, element, effect, size) + end + + if is_module_focused("colorequal") then + return handle_color_module("colorequal", knob_idx, element, effect, size) + end + + -- Try sigmoid module + if is_module_focused("sigmoid") then + local result = handle_sigmoid(knob_idx, element, effect, size) + if result then + return result + end + end + + -- Try primaries module + if is_module_focused("primaries") then + local result = handle_primaries(knob_idx, element, effect, size) + if result then + return result + end + end + + -- Try agx module + if is_module_focused("agx") then + local result = handle_agx(knob_idx, element, effect, size) + if result then + return result + end + end + + -- Try tone equalizer + if is_module_focused("toneequal") then + return handle_tone_equalizer(knob_idx, element, effect, size) + end + + -- Try channel mixer (color calibration) + if is_module_focused("channelmixerrgb") then + local result = handle_channel_mixer(knob_idx, element, effect, size) + if result then + return result + end + end + + -- Try color balance contrast + if is_module_focused("colorbalancergb") then + local result = handle_color_balance_contrast(knob_idx, element, effect, size) + if result then + return result + end + end + + -- Fall back to default mapping + return handle_default_mapping(knob_idx, element, effect, size) +end + +-- Setup mimic sliders +local function setup_knobs() + for k = 1, NUM_KNOBS do + dt.gui.mimic("slider", "knob " .. k, + function(action, element, effect, size) + local knob_idx = tonumber(action:sub(-1)) + return handle_knob_action(knob_idx, element, effect, size) + end) + end +end + +-- Script metadata +local script_data = { + metadata = { + name = _("x-touch"), + purpose = _("example of how to control an x-touch midi device"), + author = "Diederik ter Rahe", + help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/x-touch" + }, + destroy = function() end, + destroy_method = nil, + restart = nil, + show = nil +} + +-- Initialize +setup_knobs() return script_data