diff --git a/BigClockWithExtras/BigClockExtra.lua b/BigClockWithExtras/BigClockExtra.lua new file mode 100644 index 0000000..d600fe4 --- /dev/null +++ b/BigClockWithExtras/BigClockExtra.lua @@ -0,0 +1,311 @@ +-- Reaper scripted "Big Clock" window with extra functions +-- - Normal time display as in the usual "Big Clock" +-- - Time since last project marker +-- - Time since last region start +-- Author: Ludwig Frühschütz +-- Source: https://www.eleton-audio.de +-- Git: https://files.eleton-audio.de/gitea/Ludwig/Reaper-Scripts.git +-- License: GPL v3.0 +-- Requires: Reaper 5 or 6 +-- Basic gui stuff by forum user "spk77": https://forum.cockos.com/showthread.php?t=161557 + + +-- Some adjustable settings: +local timeSinceProject = true -- EITHER this one is true OR one of the next two +local timeSinceMarker = false +local timeSinceRegion = false + +-- Nothing to adjust here anymore... +local script_path +local mouse +local Menu +local gui = {} -- contains some settings for the GUI +local quit = false +local sws_present = false + +-- Send a message to the console +function msg(m) + reaper.ShowConsoleMsg(tostring(m) .. "\n") +end + +-- Store state to project variables +function storeExtState() + if timeSinceProject then + reaper.SetProjExtState(0, 'bigclock_extra', 'timeSinceProject', '1') + else + reaper.SetProjExtState(0, 'bigclock_extra', 'timeSinceProject', '0') + end + + if timeSinceMarker then + reaper.SetProjExtState(0, 'bigclock_extra', 'timeSinceMarker', '1') + else + reaper.SetProjExtState(0, 'bigclock_extra', 'timeSinceMarker', '0') + end + + if timeSinceRegion then + reaper.SetProjExtState(0, 'bigclock_extra', 'timeSinceRegion', '1') + else + reaper.SetProjExtState(0, 'bigclock_extra', 'timeSinceRegion', '0') + end + + reaper.SetProjExtState(0, 'bigclock_extra', 'dockState', gfx.dock(-1)) +end + +--------------------------- +-- functions for graphics... +--------------------------- + +-- Returns current script's path +function get_script_path() + local info = debug.getinfo(1,'S'); + local script_path = info.source:match[[^@?(.*[\/])[^\/]-$]] + return script_path +end + +-- gui init stuff, including rightclick-menu +function gui_init() + -- Get "script path" + script_path = get_script_path() + --msg(script_path) + + -- Modify "package.path" + package.path = package.path .. ";" .. script_path .. "?.lua" + --msg(package.path) + + -- Import files ("classes", functions etc.)-- + require "class" -- import "base class" + mouse = require "mouse" + Menu = require "menu class" + + -- Create "right click" menu -- + -- Create a "Menu" instance + rc_menu = Menu("rc_menu") + + -- Add menu items to "rc_menu" + -- ">" at the start spawns a submenu, | at the end creates a spacer + rc_menu:add_item({label = "Time since project start", + toggleable = true, + selected = timeSinceProject}) + rc_menu:add_item({label = "Time since previous marker", + toggleable = true, + selected = timeSinceMarker}) + rc_menu:add_item({label = "Time since previous region", + toggleable = true, + selected = timeSinceRegion}) + rc_menu:add_item({label = "Quit"}) + + -- Let's add a command to all created items: + rc_menu.items[1].command = function() + if rc_menu.items[1].selected then + timeSinceProject = true + timeSinceMarker = false + timeSinceRegion = false + rc_menu.items[2].selected = false + rc_menu.items[3].selected = false + else + --dont let user disable option but force selection of other option + rc_menu.items[1].selected = true; + end + storeExtState() + end + rc_menu.items[2].command = function() + if rc_menu.items[2].selected then + timeSinceProject = false + timeSinceMarker = true + timeSinceRegion = false + rc_menu.items[1].selected = false + rc_menu.items[3].selected = false + else + --dont let user disable option but force selection of other option + rc_menu.items[2].selected = true; + end + storeExtState() + end + rc_menu.items[3].command = function() + if rc_menu.items[3].selected then + timeSinceProject = false + timeSinceMarker = false + timeSinceRegion = true + rc_menu.items[1].selected = false + rc_menu.items[2].selected = false + else + --dont let user disable option but force selection of other option + rc_menu.items[3].selected = true; + end + storeExtState() + end + rc_menu.items[4].command = function() quit = true end +end + +-- Draw GUI -- +function drawGui() + local retval = 0 + local val + local timeRaw + local timeDisplay + local markerId + local regionId + local isRegion + local markerRegionPos + local regionEnd + local markerRegionName + local markerRegionNumber + + -- get time depending on play state + if reaper.GetAllProjectPlayStates(0) == 0 then -- 0: stopped + timeRaw = reaper.GetCursorPosition() + else -- 1: play; 2: pause; 4: record (and their bitwise |) + timeRaw = reaper.GetPlayPosition() + end + + -- calculate time since previous marker or region + markerId, regionId = reaper.GetLastMarkerAndCurRegion(0, timeRaw) + if timeSinceMarker then + retval, isRegion, markerRegionPos, regionEnd, markerRegionName, markerRegionNumber = reaper.EnumProjectMarkers(markerId) + elseif timeSinceRegion then + retval, isRegion, markerRegionPos, regionEnd, markerRegionName, markerRegionNumber = reaper.EnumProjectMarkers(regionId) + end + if retval > 0 then timeRaw = timeRaw - markerRegionPos end + + timeDisplay = math.floor(timeRaw/60) .. ":" .. math.floor((timeRaw%60)*1000)/1000 + + gfx.clear = 3355443 -- background is dark grey + + -- If window is docked, use different scaling + if gfx.dock(-1) > 0 then + -- landscape format + if gfx.w > gfx.h then + gfx.x = 20 + gfx.y = ( gfx.h-select(2, gfx.measurestr(timeDisplay)) ) / 2 + gui.settings.font_size = gfx.h / 2 + gfx.setfont(1,"Arial", gui.settings.font_size) + -- check if string fits into window, if not decrease fontsize a little, check again and loop + while gfx.measurestr(timeDisplay) > (gfx.w - 2*gfx.x) do + gui.settings.font_size = gui.settings.font_size / 1.1 + gfx.setfont(1,"Arial", gui.settings.font_size) + end + gfx.printf(timeDisplay) + -- portrait format + else + gfx.x = gfx.w/15 + gfx.y = ( gfx.h-select(2, gfx.measurestr(timeDisplay)) ) / 2 + gui.settings.font_size = gfx.w / 3 + gfx.setfont(1,"Arial", gui.settings.font_size) + gfx.printf(timeDisplay) + end + else -- window is not docked + -- landscape format + if gfx.w > gfx.h then + gfx.x = gfx.w/20 + gfx.y = ( gfx.h-select(2, gfx.measurestr(timeDisplay)) ) / 2 + gui.settings.font_size = gfx.w / 6 + gfx.setfont(1,"Arial", gui.settings.font_size) + -- check if string fits into window, if not decrease fontsize a little, check again and loop + while gfx.measurestr(timeDisplay) > (gfx.w - 2*gfx.x) do + gui.settings.font_size = gui.settings.font_size / 1.1 + gfx.setfont(1,"Arial", gui.settings.font_size) + end + gfx.printf(timeDisplay) + -- portrait format + else + gfx.x = gfx.w/20 + gfx.y = ( gfx.h-select(2, gfx.measurestr(timeDisplay)) ) / 2 + gui.settings.font_size = gfx.w / 3 + gfx.setfont(1,"Arial", gui.settings.font_size) + gfx.printf(timeDisplay) + end + end +end + +-- Called on script termination. Store stuff... +function onExit() + storeExtState() +end + +-- Main init function +function init() + -- check for SWS extensions + if reaper.NamedCommandLookup('_SWS_ABOUT') > 0 then sws_present = true end + -- load settings from rpp + local retval + local val + retval, val = reaper.GetProjExtState(0, 'bigclock_extra', 'timeSinceProject') + if retval > 0 then + if tonumber(val) > 0 then + timeSinceProject = true + else + timeSinceProject = false + end + end + retval, val = reaper.GetProjExtState(0, 'bigclock_extra', 'timeSinceMarker') + if retval > 0 then + if tonumber(val) > 0 then + timeSinceMarker = true + else + timeSinceMarker = false + end + end + retval, val = reaper.GetProjExtState(0, 'bigclock_extra', 'timeSinceRegion') + if retval > 0 then + if tonumber(val) > 0 then + timeSinceRegion = true + else + timeSinceRegion = false + end + end + + -- init stuff... + gui_init() + + -- Add stuff to "gui" table + gui.settings = {} -- Add "settings" table to "gui" table + gui.settings.font_size = 50 -- font size + gui.settings.docker_id = 0 -- try 0, 1, 257, 513, 1027 etc. + + -- Initialize gfx window -- + gfx.init("Big Clock Extra", 350, 100, gui.settings.docker_id) + gfx.setfont(1,"Arial", gui.settings.font_size) + gfx.clear = 3355443 -- background is dark grey + + -- Restore docked state + retval, val = reaper.GetProjExtState(0, 'bigclock_extra', 'dockState') + if retval > 0 then + gfx.dock(val) + end +end + +-- Main loop -- +function mainloop() + -- mouseclicks? + local RMB_state = mouse.cap(mouse.RB) + local LMB_state = mouse.cap(mouse.LB) + local mx = gfx.mouse_x + local my = gfx.mouse_y + + if not mouse.last_RMB_state and gfx.mouse_cap&2 == 2 then + -- right click pressed down -> show "right click menu" at mouse cursor + rc_menu:show(mx, my) + if sws_present then + reaper.Main_OnCommand( reaper.NamedCommandLookup('_BR_FOCUS_ARRANGE_WND'), 0 ) -- focus arranger + end + end + + if not mouse.last_LMB_state and gfx.mouse_cap&1 == 1 then + -- left click pressed down + if sws_present then + reaper.Main_OnCommand( reaper.NamedCommandLookup('_BR_FOCUS_ARRANGE_WND'), 0 ) -- focus arranger + end + end + + mouse.last_RMB_state = RMB_state -- store current right mouse button state + mouse.last_LMB_state = LMB_state -- store current left mouse button state + + drawGui() + gfx.update() + if gfx.getchar() >= 0 and not quit then reaper.defer(mainloop) end +end + +-- START HERE... +reaper.atexit(onExit) +init() +mainloop() diff --git a/BigClockWithExtras/class.lua b/BigClockWithExtras/class.lua new file mode 100644 index 0000000..6cc159c --- /dev/null +++ b/BigClockWithExtras/class.lua @@ -0,0 +1,49 @@ +-- Basic gui stuff by forum user "spk77": https://forum.cockos.com/showthread.php?t=161557 +------------- "class.lua" is copied from http://lua-users.org/wiki/SimpleLuaClasses ----------- + +-- class.lua +-- Compatible with Lua 5.1 (not 5.0). +function class(base, init) + local c = {} -- a new class instance + if not init and type(base) == 'function' then + init = base + base = nil + elseif type(base) == 'table' then + -- our new class is a shallow copy of the base class! + for i,v in pairs(base) do + c[i] = v + end + c._base = base + end + -- the class will be the metatable for all its objects, + -- and they will look up their methods in it. + c.__index = c + + -- expose a constructor which can be called by () + local mt = {} + mt.__call = function(class_tbl, ...) + local obj = {} + setmetatable(obj,c) + if init then + init(obj,...) + else + -- make sure that any stuff from the base class is initialized! + if base and base.init then + base.init(obj, ...) + end + end + return obj + end + c.init = init + c.is_a = function(self, klass) + local m = getmetatable(self) + while m do + if m == klass then return true end + m = m._base + end + return false + end + setmetatable(c, mt) + return c +end +---------------------------------------------------------------------------------------- diff --git a/BigClockWithExtras/menu class.lua b/BigClockWithExtras/menu class.lua new file mode 100644 index 0000000..115374f --- /dev/null +++ b/BigClockWithExtras/menu class.lua @@ -0,0 +1,191 @@ +-- Basic gui stuff by forum user "spk77": https://forum.cockos.com/showthread.php?t=161557 +---------------- +-- Menu class -- +---------------- + +-- To create a new menu instance, call this function like this: +-- menu_name = Menu("menu_name") +local Menu = + class( + function(menu, id) + menu.id = id + menu.items = {} -- Menu items are collected to this table + menu.items_str = "" + menu.curr_item_pos = 1 + end + ) + +------------------ +-- Menu methods -- +------------------ + +--[[ +-- True if menu item label starts with "prefix" +function Menu:label_starts_with(label, prefix) + return string.sub(label, 1, string.len(prefix)) == prefix +end +--]] + + +-- Returns "menu item table" (or false if "id" not found) +function Menu:get_item_from_id(id) + for i=1, #self.items do + if self.items[i].id == id then + return self.items[i] + end + end + return false +end + + +-- Updates "menu item type" variables (_has_submenu, _last_item_in_submenu etc.) +function Menu:update_item(item_table) + local t = item_table + t._has_submenu = false + t._last_item_in_submenu = false + t.id = self.curr_item_pos + + if string.sub(t.label, 1, 1) == ">" or + string.sub(t.label, 1, 2) == "<>" or + string.sub(t.label, 1, 2) == "><" then + t._has_submenu = true + t.id = -1 + self.curr_item_pos = self.curr_item_pos - 1 + --end + elseif string.sub(t.label, 1, 1) == "<" then + t._has_submenu = false + t._last_item_in_submenu = true + end + --t.id = self.curr_item_pos + self.curr_item_pos = self.curr_item_pos + 1 +end + + +-- Returns the created table and table index in "menu_obj.items" +function Menu:add_item(...) + local t = ... or {} + self.items[#self.items+1] = t -- add new menu item at the end of menu + + -- Parse arguments + for i,v in pairs(t) do + --msg(i .. " = " .. tostring(v)) + if i == "label" then + t.label = v + elseif i == "selected" then + t.selected = v + elseif i == "active" then + t.active = v + elseif i == "toggleable" then + t.toggleable = v + elseif i == "command" then + t.command = v + end + end + + -- Default values for menu items + -- (Edit these) + if t.label == nil or t.label == "" then + t.label = tostring(#self.items) -- if label is nil or "" -> label is set to "table index in menu_obj.items" + end + + if t.selected == nil then + t.selected = false -- edit + end + + if t.active == nil then + t.active = true -- edit + end + + if t.toggleable == nil then + t.toggleable = false -- edit + end + + return t, #self.items +end + + +-- Get menu item table at index +function Menu:get_item(index) + if self.items[index] == nil then + return false + end + return self.items[index] +end + + +-- Show menu at mx, my +function Menu:show(mx, my) + gfx.x = mx + gfx.y = my + + -- Check which items has a function to call when a menu is about to be shown + for i=1, #self.items do + if self.items[i].on_menu_show ~= nil then + self.items[i].on_menu_show() + end + -- Update item + self:update_item(self.items[i]) + end + + -- Convert menu item tables to string + self.items_str = self:table_to_string() or "" + self.val = gfx.showmenu(self.items_str) + if self.val > 0 then + self:update(self.val) + end + self.curr_item_pos = 1 -- set "menu item position counter" back to the initial value +end + + +function Menu:update(menu_item_index) + -- check which "menu item id" matches with "menu_item_index" + for i=1, #self.items do + if self.items[i].id == menu_item_index then + menu_item_index = i + break + end + end + local i = menu_item_index + -- if menu item is "toggleable" then toggle "selected" state + if self.items[i].toggleable then + self.items[i].selected = not self.items[i].selected + end + -- if menu item has a "command" (function), then call that function + if self.items[i].command ~= nil then + self.items[i].command() + end +end + + +-- Convert "Menu_obj.items" to string +function Menu:table_to_string() + if self.items == nil then + return + end + self.items_str = "" + + for i=1, #self.items do + local temp_str = "" + local menu_item = self.items[i] + if menu_item.selected then + temp_str = "!" + end + + if not menu_item.active then + temp_str = temp_str .. "#" + end + + if menu_item.label ~= "" then + temp_str = temp_str .. menu_item.label .. "|" + end + + self.items_str = self.items_str .. temp_str + end + + return self.items_str +end + +--END of Menu class---------------------------------------------------- + +return Menu + diff --git a/BigClockWithExtras/mouse.lua b/BigClockWithExtras/mouse.lua new file mode 100644 index 0000000..7d0d648 --- /dev/null +++ b/BigClockWithExtras/mouse.lua @@ -0,0 +1,35 @@ +-- Basic gui stuff by forum user "spk77": https://forum.cockos.com/showthread.php?t=161557 +----------------- +-- Mouse table -- +----------------- + +local mouse = { + -- Constants + LB = 1, + RB = 2, + CTRL = 4, + SHIFT = 8, + ALT = 16, + + -- "cap" function + cap = function (mask) + if mask == nil then + return gfx.mouse_cap end + return gfx.mouse_cap&mask == mask + end, + + uptime = 0, + + last_x = -1, last_y = -1, + + dx = 0, + dy = 0, + + ox_l = 0, oy_l = 0, -- left click positions + ox_r = 0, oy_r = 0, -- right click positions + capcnt = 0, + last_LMB_state = false, + last_RMB_state = false + } + +return mouse