Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Demopaja Timeline

Back in my youth I was hugely inspired by the timeline component of Demopaja, Moppi Productions' GUI tool for making demos. It turned out to be a very professional and conceptual product, with a plugin system tightly integrated into the editor. In my humble opinion, it was hardly inferior to the commercial products of its time — frankly, even today, about 25 years later, it still looks remarkably good.

Time went by, and around 2020 I met cool lightweight libraries built on using immediate-mode concept — Nuklear and Dear ImGui. The stars aligned, and I got the idea of showing that complex GUI components can be recreated utilizing these concepts. So I decided to port the timeline component to show that there is no need to rely on heavy frameworks like MFC to implement complex, composite and, most importantly, portable GUI controls nowadays.

Luckily, by that time Memon of Moppi had already released the Demopaja source code. In 2022 I took it on and set out to port it to Lua with the Nuklear library. Within roughly three months of spare-time coding the core code was ported and adapted. But it was full of small and annoying bugs, some of them were not so easy to localize and fix. After several iterations I got tired of it and put the project "on the shelf".

And so, in 2026, I decided to use the power of AI to merge the two sources to finally get the work done. AI fixed all the bugs much better and faster than me :-)

All original copyrights go to Demopaja author Mikko Mononen (https://github.com/memononen). All the graphic resources — the cursors and the interface sprites — are borrowed from Demopaja as well.

I hope someone finds this project useful, feel free to use and modify it according to your own needs.

preview

Requirements

Dependency Purpose Where to get it
Lua 5.3 interpreter distro package
LuaSocket timers (socket.gettime) distro package lua-socket
MoonGL OpenGL bindings build from source
MoonGLFW window/input bindings build from source
MoonNuklear Nuklear UI bindings build from source
MoonImage PNG/JPG loading build from source
GLFW 3, GLEW native libs behind the bindings distro packages
OpenGL 3.3 + X11 display rendering driver / WSLg on Windows

json.lua (atlas descriptors), fonts, icon atlases and pixel cursors are bundled in the repository — nothing else to fetch.

Installation

Debian/Ubuntu (including WSL2 with WSLg):

sudo apt install lua5.3 liblua5.3-dev lua-socket libglfw3-dev libglew-dev

Then build the four Moon* bindings. Each one follows the same routine (the Makefile detects the Lua version and installs into /usr/local):

for lib in moongl moonglfw moonimage moonnuklear; do
   git clone --depth 1 https://github.com/stetre/$lib
   make -C $lib
   sudo make -C $lib install
done

Verify the setup:

lua5.3 -e "require('moongl'); require('moonglfw'); require('moonnuklear'); require('moonimage'); print('ok')"

Running

From the repository root (resource paths are relative):

lua5.3 timelinebar.lua

Configuration knobs at the top of timelinebar.lua:

  • GLOBAL_SCALE — UI scale for HiDPI screens (default 2; set 1 for a 1:1 pixel match with the original).
  • START_THEME'dark' or 'light'; also switchable at runtime with the button in the top-left corner.

Controls

  • Left click — select rows/keys; drag keys by time, curve points by value and time (the original moved them vertically only); drag the bottom edge of an expanded curve to resize it.
  • Double click — add a key on a curve or a segment; add a marker on the waveform strip.
  • Right click — context menus: layers/effects (rename, insert, delete), key interpolation and TCB/ease, out-of-range modes, markers, ruler zoom.
  • Box select — drag in an empty timeline area; drag any selected key to move the whole selection. Inside expanded curve blocks keys are picked by their curve points; when the box spans several blocks, the block with the most captured keys wins.
  • Space + drag — pan; mouse wheel — scroll the list.
  • Enter — play/stop; arrows — step frames (with Ctrl: markers, with Shift: beats); Ctrl+Home/End — jump to start/end; Esc — deselect; Ctrl+Q — quit.

Tests

lua5.3 tests.lua

129 headless checks of the model/document/theme/utility/container layers — no GL or window required. The exit code is the number of failures.

Layout

timelinebar.lua          entry point: config, GLFW/GL/nuklear init, main loop
tests.lua                headless tests (model, doc, theme, util, const)
timeline/
  util.lua               C integer semantics, CRect, NiceNum, font metrics
  const.lua              all original constants (flags, hits, tracking)
  theme.lua              light/dark palettes, in-place theme switching
  resources.lua          texture atlases (per-theme cache), pixel cursors
  model.lua              corelib model: keys, segments, TCB controllers, scene
  doc.lua                CDemopajaDoc-style edit operations, snapshot undo
  render.lua             CDC replacement: canvas + scissor stack + scaling
  widgets.lua            timers, popup menus, scrollbars, buttons
  container.lua          IContainer: pluggable editors for expanded blocks
                         (CurveContainer, CurveColorContainer, custom ones)
  core.lua               TimeLineBarC: constructor, layout, themes
  draw.lua               TimeLineBarC: all drawing
  hittest.lua            TimeLineBarC: hit tests
  input.lua              TimeLineBarC: mouse/keyboard/menus/transport
  demo_scene.lua         demo content

Port sources: Demopaja_050508/demopaja/TimeLineBar.{h,cpp} and corelib/. Method and field names follow the original C++ so the code can be compared side by side. Known omissions: type-in dialogs, the full undo stack (a cancel snapshot is kept instead), clipboard, music waveform.

Custom blocks

The expanded area below a parameter row is owned by an IContainer (timeline/container.lua): it reports its size, draws itself and handles mouse events, returning control to the bar for anything it does not consume. The curve editor is CurveContainer; colors use its subclass CurveColorContainer (gradient background, byte scale). To draw a completely new block, implement the interface and register it:

local Containers = require('timeline.container')

local MyBlockC = Containers.subclass(Containers.IContainerC)
function MyBlockC:draw(view, render)
   -- view carries the doc, the row, geometry and time mapping helpers
end

Containers.register(C.PARAM_TYPE_TEXT, MyBlockC)  -- for a parameter type
Containers.set_class(pParam, MyBlockC)            -- for a single parameter

Bottom-edge resizing comes from the base class for free; override hit_test/on_lbutton_down/get_cursor to add interaction.

About

Immediate mode GUI timeline implementation

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages