Colored text

Colour belongs to the draw, not to the string: each run is natiny.render.set_color followed by natiny.render.text, with natiny.font.measure_text moving the pen to the next one. The same runs are drawn twice, once dark and offset, for the shadow.


    
main.lua
local natiny = require("natiny")

natiny.backend.init()

local win = natiny.window.create("Colored text", 900, 600)
natiny.window.set_scale_mode(win, natiny.window.SCALE_MODE_STRETCH)

local res  = natiny.resource.load("data/common/fonts/roboto.ttf")
local font = natiny.font.create(res, 26)
local small = natiny.font.create(res, 18)

local BG     = {  21 / 255,  30 / 255,  44 / 255 } -- #151E2C
local PANEL  = {  15 / 255,  22 / 255,  33 / 255 } -- #0F1621
local OBJECT = { 169 / 255, 179 / 255, 197 / 255 } -- #A9B3C5
local LIGHT_TEXT = { 243 / 255, 245 / 255, 250 / 255 } -- #F3F5FA

-- One colour to a kind of token. There is no rich text: a run is a colour and
-- a string, and the pen keeps its place between them.
local INK = {
    keyword = { 198 / 255, 146 / 255, 233 / 255 },
    call    = { 122 / 255, 190 / 255, 255 / 255 },
    string  = { 168 / 255, 194 / 255,  56 / 255 },
    number  = { 244 / 255, 178 / 255, 102 / 255 },
    comment = { 110 / 255, 122 / 255, 140 / 255 },
    plain   = LIGHT_TEXT,
}

local LINES = {
    { { "-- one colour, one draw, one pen", "comment" } },
    { { "local ", "keyword" }, { "message ", "plain" }, { "= ", "plain" },
      { "\"hello\"", "string" } },
    { { "for ", "keyword" }, { "i ", "plain" }, { "= ", "plain" },
      { "1", "number" }, { ", ", "plain" }, { "8 ", "number" },
      { "do", "keyword" } },
    { { "    print", "call" }, { "(message, i * ", "plain" },
      { "1.5", "number" }, { ")", "plain" } },
    { { "end", "keyword" } },
}

local X, Y = 90, 220
local SHADOW = 3

-- Draws one line of runs and answers how wide it turned out. Every run is an
-- ordinary text draw; measure_text is what moves the pen past the last one.
local function draw_line(y, runs, color)
    local pen = X
    for _, run in ipairs(runs) do
        local ink = color or INK[run[2]]
        natiny.render.set_color(ink[1], ink[2], ink[3], color and 0.5 or 1)
        natiny.render.text(font, pen, y, run[1])
        pen = pen + natiny.font.measure_text(font, run[1])
    end
    return pen - X
end

natiny.backend.loop(function()
    natiny.window.bind(win, function()
        local line_height = natiny.font.get_line_height(font)
        local height = line_height * #LINES

        natiny.render.clear(BG[1], BG[2], BG[3], 1)

        natiny.render.set_color(PANEL[1], PANEL[2], PANEL[3], 1)
        natiny.render.rounded_rectangle(X - 30, Y - 26, 720, height + 52, 10)
        natiny.render.set_color(OBJECT[1], OBJECT[2], OBJECT[3], 0.25)
        natiny.render.rounded_rectangle_outline(X - 30, Y - 26, 720, height + 52, 10, 10, 10, 10, 2)

        for i, runs in ipairs(LINES) do
            local y = Y + (i - 1) * line_height
            -- The same runs twice: a dark pass offset behind, then the colours.
            draw_line(y + SHADOW, runs, { 0, 0, 0 })
            draw_line(y, runs)
        end

        natiny.render.set_color(LIGHT_TEXT[1], LIGHT_TEXT[2], LIGHT_TEXT[3], 1)
        natiny.render.text(small, X - 30, 70,
            "set_color, text, set_color, text - the colour belongs to the draw")

        local legend, pen = { "keyword", "call", "string", "number", "comment" }, X - 30
        for _, kind in ipairs(legend) do
            local ink = INK[kind]
            natiny.render.set_color(ink[1], ink[2], ink[3], 1)
            natiny.render.rectangle(pen, Y + height + 78, 12, 12)
            natiny.render.text(small, pen + 20, Y + height + 72, kind)
            pen = pen + 40 + natiny.font.measure_text(small, kind)
        end
    end)
end)

natiny.backend.shutdown()