Login
gpcr/spec/pus_lvgl_spec.cr
Login

[edit]

spec/pus_lvgl_spec.cr @ trunk

spec

require 'spec'
scale = 0.75
fontscale = 1.500
box wid 1.380 ht 0.340 with .nw at (0.035, -0.035) fill 0xCBD5E1 color 0xCBD5E1
box wid 1.380 ht 0.340 with .nw at (0.000, 0.000) fill 0x7C83E6 color 0x7C83E6
box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.022) fill 0xffffff color 0xffffff
line from (0.140, 0.000) to (0.140, -0.340) color white
line from (1.240, 0.000) to (1.240, -0.340) color white
text "require 'spec'" color white at (0.690, -0.170)

crystal source

require "spec"

json block

{
  "kind": "require",
  "name": "spec",
  "doc": null,
  "children": []
}

../src/gpcr/pus_lvgl

require '../src/gpcr/pus_lvgl'
scale = 0.75
fontscale = 1.500
box wid 2.481 ht 0.340 with .nw at (0.035, -0.455) fill 0xCBD5E1 color 0xCBD5E1
box wid 2.481 ht 0.340 with .nw at (0.000, -0.420) fill 0x7C83E6 color 0x7C83E6
box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.443) fill 0xffffff color 0xffffff
line from (0.140, -0.420) to (0.140, -0.760) color white
line from (2.341, -0.420) to (2.341, -0.760) color white
text "require '../src/gpcr/pus_lvgl'" color white at (1.241, -0.590)

crystal source

require "../src/gpcr/pus_lvgl"

json block

{
  "kind": "require",
  "name": "../src/gpcr/pus_lvgl",
  "doc": null,
  "children": []
}

PUS_SAMPLE = <<-PIKCHR
scale = 1
start_btn: box wid 1.00 ht 0.50 rad 0.10 with .nw at (0.00,2.00) fill 0x112233 color 0x445566 # id=start_btn op={"op":"call","target":"blink"} ev=clicked
text "Start" color 0xFFFFFF at (0.50,1.75)
line from (0.00,1.00) to (1.00,1.00) color 0xCCCCCC
ellipse wid 0.20 ht 0.20 at (1.50,1.50) fill 0xFF0000 color 0x00FF00
PIKCHR

describe GPCR::PUS do
  it "parses admitted PUS-1 elements and annotations" do
    doc = GPCR::PUS.parse(PUS_SAMPLE)
    doc.scale.should eq(1.0)
    doc.elements.map(&.kind).should eq([:box, :text, :line, :ellipse])

    box = doc.elements[0]
    box.tag.should eq("start_btn")
    box.annotation.id.should eq("start_btn")
    box.annotation.op.should eq(%({"op":"call","target":"blink"}))
    box.annotation.ev.should eq("clicked")
  end

  it "resolves Pikchr inches to lvgl pixels with top-left Y inversion" do
    plan = GPCR::PUS.to_lvgl_plan(PUS_SAMPLE)
    plan.canvas_height.should eq(2.0)

    box = plan.operations[0]
    box.bindable.should be_true
    box.code.should contain("box_1 = Lvgl::Widgets::Button.new(PARENT)")
    box.code.should contain("box_1.set_pos(0, 0)")
    box.code.should contain("box_1.set_size(144, 72)")
    box.code.should contain("box_1.set_style_radius(14)")
  end

  it "parents contained text and centers it relative to the containing box" do
    plan = GPCR::PUS.to_lvgl_plan(PUS_SAMPLE)
    text = plan.operations[1]

    text.bindable.should be_true
    text.code.should contain("text_2 = Lvgl::Widgets::Label.new(box_1)")
    text.code.should contain(%(text_2.text = "Start"))
    text.code.should contain("text_2.long_mode = Lvgl::LabelLongMode::Clip")
    text.code.should contain("text_2.set_size(77, 23)")
    text.code.should contain("text_2.set_style_text_align(Lvgl::TextAlign::Center, Lvgl.style_selector)")
    text.code.should contain("text_2.align(Lvgl::Align::Center, offset: {0, 0})")
    text.note.should eq("child of box_1")
  end

  it "emits real line widgets while keeping ellipse explicitly unsupported" do
    plan = GPCR::PUS.to_lvgl_plan(PUS_SAMPLE)

    line = plan.operations[2]
    line.kind.should eq(:line)
    line.bindable.should be_true
    line.code.should contain("line_3 = Lvgl::Widgets::Line.new(PARENT)")
    line.code.should contain("line_3.points = [{0, 144}, {144, 144}]")
    line.code.should contain("line_3.set_style_line_color(Lvgl::Color.hex(0xCCCCCC))")

    ellipse = plan.operations[3]
    ellipse.kind.should eq(:ellipse)
    ellipse.bindable.should be_false
    ellipse.note.not_nil!.should contain("lv_arc_create")
  end

  it "emits inspectable lvgl-crystal code with line widgets and missing ellipse comments" do
    code = GPCR::PUS.emit_lvgl_code(GPCR::PUS.to_lvgl_plan(PUS_SAMPLE), "screen")
    code.should contain("Lvgl::Widgets::Button.new(screen)")
    code.should contain("Lvgl::Widgets::Line.new(screen)")
    code.should contain("dispatch_op")
    code.should_not contain("# MISSING_BINDING line")
    code.should contain("# MISSING_BINDING ellipse")
  end

  it "lowers open N-point polylines through the retained Line point buffer" do
    plan = GPCR::PUS.to_lvgl_plan("scale = 1\nline from (0,2) to (1,1) then to (2,2) color 0x123456\n")
    line = plan.operations[0]
    line.bindable.should be_true
    line.code.should contain("line_1.points = [{0, 0}, {144, 144}, {288, 0}]")
  end

  it "lowers predefined-process vertical bars through line widgets" do
    plan = GPCR::PUS.to_lvgl_plan("scale = 1\nline from (0.140, 0.000) to (0.140, -0.340) color white\n")
    line = plan.operations[0]
    line.bindable.should be_true
    line.code.should contain("line_1 = Lvgl::Widgets::Line.new(PARENT)")
    line.code.should contain("line_1.points = [{20, 0}, {20, 49}]")
    line.code.should contain("line_1.set_style_line_color(Lvgl::Color.hex(0xFFFFFF))")
  end

  it "lowers skinny divider boxes through line widgets with pixel thickness" do
    plan = GPCR::PUS.to_lvgl_plan("scale = 1\nbox wid 1.00 ht 0.05 rad 0.02 with .nw at (0,1) fill 0xABCDEF color 0xABCDEF\n")
    line = plan.operations[0]
    line.bindable.should be_true
    line.code.should contain("box_1 = Lvgl::Widgets::Line.new(PARENT)")
    line.code.should contain("box_1.points = [{0, 3}, {144, 3}]")
    line.code.should contain("box_1.set_style_line_width(7)")
    line.code.should contain("box_1.set_style_line_rounded(true)")
  end

  it "lowers skinny vertical side bars through line widgets" do
    plan = GPCR::PUS.to_lvgl_plan("scale = 1\nbox wid 0.04 ht 0.50 with .nw at (0,1) fill 0x111111 color 0x111111\n")
    line = plan.operations[0]
    line.bindable.should be_true
    line.code.should contain("box_1 = Lvgl::Widgets::Line.new(PARENT)")
    line.code.should contain("box_1.points = [{3, 0}, {3, 72}]")
    line.code.should contain("box_1.set_style_line_width(6)")
  end

  it "rejects elements outside the admitted subset" do
    expect_raises(GPCR::PUS::Error, /circle is outside/) do
      GPCR::PUS.parse("scale = 1\ncircle rad 0.1 at (0,0) fill 0x000000 color 0x000000\n")
    end
  end

  it "normalizes named text colors emitted by the GPCR renderer" do
    plan = GPCR::PUS.to_lvgl_plan(%(scale = 1\ntext "label" color white at (0,0)\n))
    plan.operations[0].code.should contain("Lvgl::Color.hex(0xFFFFFF)")
  end
end