From 6f653eb1b8f9733ddd89848224136ba5b734f22c Mon Sep 17 00:00:00 2001 From: Pascal Thomet Date: Sat, 13 Jan 2024 18:48:55 +0100 Subject: [PATCH] nanovg: work on python (demo + bindings) --- .../demos_nanovg/demo_nanovg_full.cpp | 16 +- .../demo_nanovg_full_impl.cpp | 2 +- .../demos_nanovg/demo_nanovg_full.py | 105 ++ .../demos_nanovg/demo_nanovg_full_impl.py | 1001 +++++++++++++++++ .../demos_nanovg/demo_nanovg_heart.py | 2 +- bindings/imgui_bundle/nanovg.pyi | 253 +++-- external/nanovg/bindings/generate_nanovg.py | 9 +- external/nanovg/bindings/pybind_nanovg.cpp | 156 ++- external/nanovg/nvg_imgui/nvg_cpp_text.cpp | 80 ++ external/nanovg/nvg_imgui/nvg_cpp_text.h | 71 ++ 10 files changed, 1523 insertions(+), 172 deletions(-) create mode 100644 bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_full.py create mode 100644 bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_full_impl.py create mode 100644 external/nanovg/nvg_imgui/nvg_cpp_text.cpp create mode 100644 external/nanovg/nvg_imgui/nvg_cpp_text.h diff --git a/bindings/imgui_bundle/demos_cpp/demos_nanovg/demo_nanovg_full.cpp b/bindings/imgui_bundle/demos_cpp/demos_nanovg/demo_nanovg_full.cpp index 707345e1..fa358651 100644 --- a/bindings/imgui_bundle/demos_cpp/demos_nanovg/demo_nanovg_full.cpp +++ b/bindings/imgui_bundle/demos_cpp/demos_nanovg/demo_nanovg_full.cpp @@ -1,9 +1,7 @@ -#define IMGUI_DEFINE_MATH_OPERATORS -#include "immapp/immapp.h" +#include "hello_imgui/hello_imgui.h" #include "demo_utils/api_demos.h" #include "imgui.h" -#include "nanovg.h" #include "nvg_imgui/nvg_imgui.h" #include "demo_nanovg_full/demo_nanovg_full_impl.h" @@ -12,7 +10,7 @@ struct MyNvgDemo { bool Blowup = false; - DemoData nvgDemoData; + DemoData nvgDemoData = {}; NVGcontext* vg; MyNvgDemo(NVGcontext* _vg) @@ -39,7 +37,7 @@ struct MyNvgDemo struct AppState { std::unique_ptr myNvgDemo; - NVGcontext * vg; + NVGcontext * vg = nullptr; std::unique_ptr myFramebuffer; @@ -58,7 +56,6 @@ int main(int, char**) HelloImGui::RunnerParams runnerParams; runnerParams.imGuiWindowParams.defaultImGuiWindowType = HelloImGui::DefaultImGuiWindowType::NoDefaultWindow; runnerParams.appWindowParams.windowGeometry.size = {1200, 900}; - ImmApp::AddOnsParams addons; runnerParams.callbacks.EnqueuePostInit([&]() { @@ -78,7 +75,10 @@ int main(int, char**) auto nvgDrawingFunction = [&](NVGcontext *vg, float width, float height) { double now = ImGui::GetTime(); - auto mousePos = ImGui::GetMousePos() - ImGui::GetMainViewport()->Pos; + ImVec2 mousePos( + ImGui::GetIO().MousePos.x - ImGui::GetMainViewport()->Pos.x, + ImGui::GetIO().MousePos.y - ImGui::GetMainViewport()->Pos.y + ); appState.myNvgDemo->Render(width, height, (int)mousePos.x, (int)mousePos.y, (float)now); }; @@ -106,6 +106,6 @@ int main(int, char**) runnerParams.fpsIdling.enableIdling = false; - ImmApp::Run(runnerParams, addons); + HelloImGui::Run(runnerParams); return 0; } diff --git a/bindings/imgui_bundle/demos_cpp/demos_nanovg/demo_nanovg_full/demo_nanovg_full_impl.cpp b/bindings/imgui_bundle/demos_cpp/demos_nanovg/demo_nanovg_full/demo_nanovg_full_impl.cpp index fa2854b3..252f97cf 100644 --- a/bindings/imgui_bundle/demos_cpp/demos_nanovg/demo_nanovg_full/demo_nanovg_full_impl.cpp +++ b/bindings/imgui_bundle/demos_cpp/demos_nanovg/demo_nanovg_full/demo_nanovg_full_impl.cpp @@ -879,7 +879,7 @@ void drawParagraph(NVGcontext* vg, float x, float y, float width, float height, const char* hoverText = "Hover your mouse over the text to see calculated caret position."; float gx,gy; int gutter = 0; - const char* boxText = "Testing\nsome multiline\ntext."; + // const char* boxText = "Testing\nsome multiline\ntext."; NVG_NOTUSED(height); nvgSave(vg); diff --git a/bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_full.py b/bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_full.py new file mode 100644 index 00000000..ebd10815 --- /dev/null +++ b/bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_full.py @@ -0,0 +1,105 @@ +# port of bindings/imgui_bundle/demos_cpp/demos_nanovg/demo_nanovg_full.cpp +from imgui_bundle import imgui, nanovg as nvg, hello_imgui, ImVec2, ImVec4 +from imgui_bundle.demos_python import demo_utils +import demo_nanovg_full_impl +from typing import List + + +nvg_imgui = nvg.nvg_imgui + + +class MyNvgDemo: + blowup: bool + nvgDemoData: demo_nanovg_full_impl.DemoData + vg: nvg.Context + + def init(self, vg: nvg.Context): + self.vg = vg + self.blowup = False + + self.nvgDemoData = demo_nanovg_full_impl.DemoData() + status = demo_nanovg_full_impl.load_demo_data(vg, self.nvgDemoData) + if status != 0: + print("Could not load demo data.") + return + + def reset(self): + demo_nanovg_full_impl.free_demo_data(self.vg, self.nvgDemoData) + + def render(self, width: float, height: float, mouse_x: float, mouse_y: float, t: float): + demo_nanovg_full_impl.render_demo(self.vg, mouse_x, mouse_y, width, height, t, self.blowup, self.nvgDemoData) + + +class AppState: + myNvgDemo: MyNvgDemo + vg: nvg.Context + myFrameBuffer: nvg_imgui.NvgFramebuffer + clear_color: List[float] + display_in_frame_buffer: bool = False + + def __init__(self): + self.clear_color = [0.3, 0.3, 0.32, 1.0] + + +def main(): + # This call is specific to the ImGui Bundle interactive manual. In a standard application, you could write: + # hello_imgui.set_assets_folder("my_assets") # (By default, HelloImGui will search inside "assets") + demo_utils.set_hello_imgui_demo_assets_folder() + + app_state = AppState() + + runner_params = hello_imgui.RunnerParams() + runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.no_default_window + runner_params.app_window_params.window_geometry.size = (1200, 900) + + def post_init(): + app_state.vg = nvg_imgui.create_nvg_context_gl(nvg_imgui.NvgCreateFlags.antialias.value | nvg_imgui.NvgCreateFlags.stencil_strokes.value) + app_state.myNvgDemo = MyNvgDemo() + app_state.myNvgDemo.init(app_state.vg) + nvg_image_flags = 0 + app_state.myFrameBuffer = nvg_imgui.NvgFramebuffer(app_state.vg, 1000, 600, nvg_image_flags) + + def before_exit(): + app_state.myNvgDemo.reset() + app_state.myFrameBuffer = None + nvg_imgui.delete_nvg_context_gl(app_state.vg) + + runner_params.callbacks.enqueue_post_init(post_init) + runner_params.callbacks.enqueue_before_exit(before_exit) + + def nvg_drawing_function(_: nvg.Context, width: float, height: float): + now = imgui.get_time() + mouse_pos = ImVec2( + imgui.get_mouse_pos().x - imgui.get_main_viewport().pos.x, + imgui.get_mouse_pos().y - imgui.get_main_viewport().pos.y) + app_state.myNvgDemo.render(width, height, mouse_pos.x, mouse_pos.y, now) + + def custom_background(): + clear_color_vec4 = ImVec4(*app_state.clear_color) + nvg_imgui.render_nvg_to_background(app_state.vg, nvg_drawing_function, clear_color_vec4) + + runner_params.callbacks.custom_background = custom_background + + def gui(): + imgui.begin("My Window!", None, imgui.WindowFlags_.always_auto_resize.value) + _, app_state.display_in_frame_buffer = imgui.checkbox("Display in framebuffer", app_state.display_in_frame_buffer) + _, app_state.myNvgDemo.blowup = imgui.checkbox("Blowup", app_state.myNvgDemo.blowup) + imgui.set_next_item_width(hello_imgui.em_size(15)) + _, app_state.clear_color = imgui.color_edit4("Clear Color", app_state.clear_color) + + if app_state.display_in_frame_buffer: + clear_color_vec4 = ImVec4(*app_state.clear_color) + nvg_imgui.render_nvg_to_frame_buffer(app_state.vg, app_state.myFrameBuffer, nvg_drawing_function, clear_color_vec4) + imgui.image(app_state.myFrameBuffer.texture_id, ImVec2(1000, 600)) + + imgui.end() + + runner_params.callbacks.show_gui = gui + + runner_params.fps_idling.enable_idling = False + + hello_imgui.run(runner_params) + + +if __name__ == "__main__": + main() diff --git a/bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_full_impl.py b/bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_full_impl.py new file mode 100644 index 00000000..6a3304c4 --- /dev/null +++ b/bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_full_impl.py @@ -0,0 +1,1001 @@ +# Port of bindings/imgui_bundle/demos_cpp/demos_nanovg/demo_nanovg_full/demo_nanovg_full_impl.cpp +from imgui_bundle import nanovg as nvg, hello_imgui + +from typing import List + +import math + + +############################################################################### +# API: port of demo_nanovg_full_impl.h +############################################################################### +class DemoData: + fontNormal: int + fontBold: int + fontIcons: int + fontEmoji: int + images: List[int] + + def __init__(self): + self.images = [-1] * 12 + + +# def load_demo_data(vg: nvg.Context) -> DemoData: +# pass +# +# +# def free_demo_data(vg: nvg.Context, data: DemoData) -> None: +# pass +# +# +# def render_demo(vg: nvg.Context, data: DemoData, mx: float, my: float, width: float, height: float, t: float, blowup: bool) -> None: +# pass + + +############################################################################### +# port of demo_nanovg_full_impl.cpp +############################################################################### +ICON_SEARCH = chr(0x1F50D) +ICON_CIRCLED_CROSS = chr(0x2716) +ICON_CHEVRON_RIGHT = chr(0xE75E) +ICON_CHECK = chr(0x2713) +ICON_LOGIN = chr(0xE740) +ICON_TRASH = chr(0xE729) + + +def clamp(x: float, mn: float, mx: float) -> float: + return min(max(x, mn), mx) + + +def is_black(c: nvg.Color) -> bool: + return c.r == 0.0 and c.g == 0.0 and c.b == 0.0 and c.a == 1.0 + + +def draw_window(vg: nvg.Context, title: str, x: float, y: float, w: float, h: float) -> None: + cornerRadius = 3.0 + + nvg.save(vg) + + # Window + nvg.begin_path(vg) + nvg.rounded_rect(vg, x, y, w, h, cornerRadius) + nvg.fill_color(vg, nvg.rgba(28, 30, 34, 192)) + nvg.fill(vg) + + # Drop shadow + shadowPaint = nvg.box_gradient(vg, x, y + 2, w, h, cornerRadius * 2, 10, nvg.rgba(0, 0, 0, 128), nvg.rgba(0, 0, 0, 0)) + nvg.begin_path(vg) + nvg.rect(vg, x - 10, y - 10, w + 20, h + 30) + nvg.rounded_rect(vg, x, y, w, h, cornerRadius) + nvg.path_winding(vg, nvg.Winding.cw.value) + nvg.fill_paint(vg, shadowPaint) + nvg.fill(vg) + + # Header + headerPaint = nvg.linear_gradient(vg, x, y, x, y + 15, nvg.rgba(255, 255, 255, 8), nvg.rgba(0, 0, 0, 16)) + nvg.begin_path(vg) + nvg.rounded_rect(vg, x + 1, y + 1, w - 2, 30, cornerRadius - 1) + nvg.fill_paint(vg, headerPaint) + nvg.fill(vg) + nvg.begin_path(vg) + nvg.move_to(vg, x + 0.5, y + 0.5 + 30) + nvg.line_to(vg, x + 0.5 + w - 1, y + 0.5 + 30) + nvg.stroke_color(vg, nvg.rgba(0, 0, 0, 32)) + nvg.stroke(vg) + + nvg.font_size(vg, 18.0) + nvg.font_face(vg, "sans-bold") + nvg.text_align(vg, nvg.Align.align_center.value | nvg.Align.align_middle.value) + + nvg.font_blur(vg, 2) + nvg.fill_color(vg, nvg.rgba(0, 0, 0, 128)) + nvg.text(vg, x + w / 2, y + 16 + 1, title) + + nvg.font_blur(vg, 0) + nvg.fill_color(vg, nvg.rgba(220, 220, 220, 160)) + nvg.text(vg, x + w / 2, y + 16, title) + + nvg.restore(vg) + + +def draw_search_box(vg: nvg.Context, text: str, x: float, y: float, w: float, h: float) -> None: + cornerRadius = h / 2 - 1 + + # Edit + bg = nvg.box_gradient(vg, x, y + 1.5, w, h, h / 2, 5, nvg.rgba(0, 0, 0, 16), nvg.rgba(0, 0, 0, 92)) + nvg.begin_path(vg) + nvg.rounded_rect(vg, x, y, w, h, cornerRadius) + nvg.fill_paint(vg, bg) + nvg.fill(vg) + + nvg.font_size(vg, h * 1.3) + nvg.font_face(vg, "icons") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 64)) + nvg.text_align(vg, nvg.Align.align_center.value | nvg.Align.align_middle.value) + nvg.text(vg, x + h * 0.55, y + h * 0.55, ICON_SEARCH) + + nvg.font_size(vg, 20.0) + nvg.font_face(vg, "sans") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 32)) + + nvg.text_align(vg, nvg.Align.align_left.value | nvg.Align.align_middle.value) + nvg.text(vg, x + h * 1.05, y + h * 0.5, text) + + nvg.font_size(vg, h * 1.3) + nvg.font_face(vg, "icons") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 32)) + nvg.text_align(vg, nvg.Align.align_center.value | nvg.Align.align_middle.value) + nvg.text(vg, x + w - h * 0.55, y + h * 0.55, ICON_CIRCLED_CROSS) + + +def draw_drop_down(vg: nvg.Context, text: str, x: float, y: float, w: float, h: float) -> None: + cornerRadius = 4.0 + + bg = nvg.linear_gradient(vg, x, y, x, y + h, nvg.rgba(255, 255, 255, 16), nvg.rgba(0, 0, 0, 16)) + nvg.begin_path(vg) + nvg.rounded_rect(vg, x + 1, y + 1, w - 2, h - 2, cornerRadius - 1) + nvg.fill_paint(vg, bg) + nvg.fill(vg) + + nvg.begin_path(vg) + nvg.rounded_rect(vg, x + 0.5, y + 0.5, w - 1, h - 1, cornerRadius - 0.5) + nvg.stroke_color(vg, nvg.rgba(0, 0, 0, 48)) + nvg.stroke(vg) + + nvg.font_size(vg, 20.0) + nvg.font_face(vg, "sans") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 160)) + nvg.text_align(vg, nvg.Align.align_left.value | nvg.Align.align_middle.value) + nvg.text(vg, x + h * 0.3, y + h * 0.5, text) + + nvg.font_size(vg, h * 1.3) + nvg.font_face(vg, "icons") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 64)) + nvg.text_align(vg, nvg.Align.align_center.value | nvg.Align.align_middle.value) + nvg.text(vg, x + w - h * 0.5, y + h * 0.5, ICON_CHEVRON_RIGHT) + + +def draw_label(vg: nvg.Context, text: str, x: float, y: float, w: float, h: float) -> None: + nvg.font_size(vg, 18.0) + nvg.font_face(vg, "sans") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 128)) + + nvg.text_align(vg, nvg.Align.align_left.value | nvg.Align.align_middle.value) + nvg.text(vg, x, y + h * 0.5, text) + + +def draw_edit_box_base(vg: nvg.Context, x: float, y: float, w: float, h: float) -> None: + # Edit + bg = nvg.box_gradient(vg, x + 1, y + 1 + 1.5, w - 2, h - 2, 3, 4, nvg.rgba(255, 255, 255, 32), nvg.rgba(32, 32, 32, 32)) + nvg.begin_path(vg) + nvg.rounded_rect(vg, x + 1, y + 1, w - 2, h - 2, 4 - 1) + nvg.fill_paint(vg, bg) + nvg.fill(vg) + + nvg.begin_path(vg) + nvg.rounded_rect(vg, x + 0.5, y + 0.5, w - 1, h - 1, 4 - 0.5) + nvg.stroke_color(vg, nvg.rgba(0, 0, 0, 48)) + nvg.stroke(vg) + + +def draw_edit_box(vg: nvg.Context, text: str, x: float, y: float, w: float, h: float) -> None: + draw_edit_box_base(vg, x, y, w, h) + + nvg.font_size(vg, 20.0) + nvg.font_face(vg, "sans") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 64)) + nvg.text_align(vg, nvg.Align.align_left.value | nvg.Align.align_middle.value) + nvg.text(vg, x + h * 0.3, y + h * 0.5, text) + + +def draw_edit_box_num(vg: nvg.Context, text: str, units: str, x: float, y: float, w: float, h: float) -> None: + draw_edit_box_base(vg, x, y, w, h) + + nvg.font_size(vg, 18.0) + nvg.font_face(vg, "sans") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 64)) + nvg.text_align(vg, nvg.Align.align_right.value | nvg.Align.align_middle.value) + nvg.text(vg, x + w - h * 0.3, y + h * 0.5, units) + + nvg.font_size(vg, 20.0) + nvg.font_face(vg, "sans") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 128)) + nvg.text_align(vg, nvg.Align.align_right.value | nvg.Align.align_middle.value) + nvg.text(vg, x + w - h * 0.5, y + h * 0.5, text) + + +def draw_check_box(vg: nvg.Context, text: str, x: float, y: float, w: float, h: float) -> None: + nvg.font_size(vg, 18.0) + nvg.font_face(vg, "sans") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 160)) + + nvg.text_align(vg, nvg.Align.align_left.value | nvg.Align.align_middle.value) + nvg.text(vg, x + 28, y + h * 0.5, text) + + bg = nvg.box_gradient(vg, x + 1, y + int(h * 0.5) - 9 + 1, 18, 18, 3, 3, nvg.rgba(0, 0, 0, 32), nvg.rgba(0, 0, 0, 92)) + nvg.begin_path(vg) + nvg.rounded_rect(vg, x + 1, y + int(h * 0.5) - 9, 18, 18, 3) + nvg.fill_paint(vg, bg) + nvg.fill(vg) + + nvg.font_size(vg, 40) + nvg.font_face(vg, "icons") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 128)) + nvg.text_align(vg, nvg.Align.align_center.value | nvg.Align.align_middle.value) + nvg.text(vg, x + 9 + 2, y + h * 0.5, ICON_CHECK) + + +def draw_button(vg: nvg.Context, preicon: str, text: str, x: float, y: float, w: float, h: float, col: nvg.Color) -> None: + cornerRadius = 4.0 + tw = 0.0 + iw = 0.0 + + bg = nvg.linear_gradient(vg, x, y, x, y + h, nvg.rgba(255, 255, 255, is_black(col) and 16 or 32), nvg.rgba(0, 0, 0, is_black(col) and 16 or 32)) + nvg.begin_path(vg) + nvg.rounded_rect(vg, x + 1, y + 1, w - 2, h - 2, cornerRadius - 1) + if not is_black(col): + nvg.fill_color(vg, col) + nvg.fill(vg) + nvg.fill_paint(vg, bg) + nvg.fill(vg) + + nvg.begin_path(vg) + nvg.rounded_rect(vg, x + 0.5, y + 0.5, w - 1, h - 1, cornerRadius - 0.5) + nvg.stroke_color(vg, nvg.rgba(0, 0, 0, 48)) + nvg.stroke(vg) + + nvg.font_size(vg, 20.0) + nvg.font_face(vg, "sans-bold") + _, tw = nvg.text_bounds(vg, 0, 0, text) + if preicon != "": + nvg.font_size(vg, h * 1.3) + nvg.font_face(vg, "icons") + _, iw = nvg.text_bounds(vg, 0, 0, preicon) + iw += h * 0.15 + + if preicon != "": + nvg.font_size(vg, h * 1.3) + nvg.font_face(vg, "icons") + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 96)) + nvg.text_align(vg, nvg.Align.align_left.value | nvg.Align.align_middle.value) + nvg.text(vg, x + w * 0.5 - tw * 0.5 - iw * 0.75, y + h * 0.5, preicon) + + nvg.font_size(vg, 20.0) + nvg.font_face(vg, "sans-bold") + nvg.text_align(vg, nvg.Align.align_left.value | nvg.Align.align_middle.value) + nvg.fill_color(vg, nvg.rgba(0, 0, 0, 160)) + nvg.text(vg, x + w * 0.5 - tw * 0.5 + iw * 0.25, y + h * 0.5 - 1, text) + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 160)) + nvg.text(vg, x + w * 0.5 - tw * 0.5 + iw * 0.25, y + h * 0.5, text) + + +def draw_slider(vg: nvg.Context, pos: float, x: float, y: float, w: float, h: float) -> None: + cy = y + (int)(h * 0.5) + kr = (int)(h * 0.25) + + nvg.save(vg) + #nvg.clear_state(vg) + + # Slot + bg = nvg.box_gradient(vg, x, cy - 2 + 1, w, 4, 2, 2, nvg.rgba(0, 0, 0, 32), nvg.rgba(0, 0, 0, 128)) + nvg.begin_path(vg) + nvg.rounded_rect(vg, x, cy - 2, w, 4, 2) + nvg.fill_paint(vg, bg) + nvg.fill(vg) + + # Knob Shadow + bg = nvg.radial_gradient(vg, x + (int)(pos * w), cy + 1, kr - 3, kr + 3, nvg.rgba(0, 0, 0, 64), nvg.rgba(0, 0, 0, 0)) + nvg.begin_path(vg) + nvg.rect(vg, x + (int)(pos * w) - kr - 5, cy - kr - 5, kr * 2 + 5 + 5, kr * 2 + 5 + 5 + 3) + nvg.circle(vg, x + (int)(pos * w), cy, kr) + nvg.path_winding(vg, nvg.Solidity.hole.value) + nvg.fill_paint(vg, bg) + nvg.fill(vg) + + # Knob + knob = nvg.linear_gradient(vg, x, cy - kr, x, cy + kr, nvg.rgba(255, 255, 255, 16), nvg.rgba(0, 0, 0, 16)) + nvg.begin_path(vg) + nvg.circle(vg, x + (int)(pos * w), cy, kr - 1) + nvg.fill_color(vg, nvg.rgba(40, 43, 48, 255)) + nvg.fill(vg) + nvg.fill_paint(vg, knob) + nvg.fill(vg) + + nvg.begin_path(vg) + nvg.circle(vg, x + (int)(pos * w), cy, kr - 0.5) + nvg.stroke_color(vg, nvg.rgba(0, 0, 0, 92)) + nvg.stroke(vg) + + nvg.restore(vg) + + +def draw_eyes(vg: nvg.Context, x: float, y: float, w: float, h: float, mx: float, my: float, t: float) -> None: + ex = w * 0.23 + ey = h * 0.5 + lx = x + ex + ly = y + ey + rx = x + w - ex + ry = y + ey + br = (ex < ey and ex or ey) * 0.5 + blink = 1 - math.pow(math.sin(t * 0.5), 200) * 0.8 + + bg = nvg.linear_gradient(vg, x, y + h * 0.5, x + w * 0.1, y + h, nvg.rgba(0, 0, 0, 32), nvg.rgba(0, 0, 0, 16)) + nvg.begin_path(vg) + nvg.ellipse(vg, lx + 3.0, ly + 16.0, ex, ey) + nvg.ellipse(vg, rx + 3.0, ry + 16.0, ex, ey) + nvg.fill_paint(vg, bg) + nvg.fill(vg) + + bg = nvg.linear_gradient(vg, x, y + h * 0.25, x + w * 0.1, y + h, nvg.rgba(220, 220, 220, 255), nvg.rgba(128, 128, 128, 255)) + nvg.begin_path(vg) + nvg.ellipse(vg, lx, ly, ex, ey) + nvg.ellipse(vg, rx, ry, ex, ey) + nvg.fill_paint(vg, bg) + nvg.fill(vg) + + dx = (mx - rx) / (ex * 10) + dy = (my - ry) / (ey * 10) + d = math.sqrt(dx * dx + dy * dy) + if d > 1.0: + dx /= d + dy /= d + dx *= ex * 0.4 + dy *= ey * 0.5 + nvg.begin_path(vg) + nvg.ellipse(vg, lx + dx, ly + dy + ey * 0.25 * (1 - blink), br, br * blink) + nvg.fill_color(vg, nvg.rgba(32, 32, 32, 255)) + nvg.fill(vg) + + dx = (mx - rx) / (ex * 10) + dy = (my - ry) / (ey * 10) + d = math.sqrt(dx * dx + dy * dy) + if d > 1.0: + dx /= d + dy /= d + dx *= ex * 0.4 + dy *= ey * 0.5 + nvg.begin_path(vg) + nvg.ellipse(vg, rx + dx, ry + dy + ey * 0.25 * (1 - blink), br, br * blink) + nvg.fill_color(vg, nvg.rgba(32, 32, 32, 255)) + nvg.fill(vg) + + dx = (mx - rx) / (ex * 10) + dy = (my - ry) / (ey * 10) + d = math.sqrt(dx * dx + dy * dy) + if d > 1.0: + dx /= d + dy /= d + dx *= ex * 0.4 + dy *= ey * 0.5 + nvg.begin_path(vg) + nvg.ellipse(vg, lx + dx, ly + dy + ey * 0.25 * (1 - blink), br * 0.5, br * blink) + nvg.fill_color(vg, nvg.rgba(32, 32, 32, 255)) + nvg.fill(vg) + + gloss = nvg.radial_gradient(vg, lx - ex * 0.25, ly - ey * 0.5, ex * 0.1, ex * 0.75, nvg.rgba(255, 255, 255, 128), nvg.rgba(255, 255, 255, 0)) + nvg.begin_path(vg) + nvg.ellipse(vg, lx, ly, ex, ey) + nvg.fill_paint(vg, gloss) + nvg.fill(vg) + + gloss = nvg.radial_gradient(vg, rx - ex * 0.25, ry - ey * 0.5, ex * 0.1, ex * 0.75, nvg.rgba(255, 255, 255, 128), nvg.rgba(255, 255, 255, 0)) + nvg.begin_path(vg) + nvg.ellipse(vg, rx, ry, ex, ey) + nvg.fill_paint(vg, gloss) + nvg.fill(vg) + + +def draw_graph(vg: nvg.Context, x: float, y: float, w: float, h: float, t: float) -> None: + samples = [0.0] * 6 + sx = [0.0] * 6 + sy = [0.0] * 6 + dx = w / 5.0 + + samples[0] = (1 + math.sin(t * 1.2345 + math.cos(t * 0.33457) * 0.44)) * 0.5 + samples[1] = (1 + math.sin(t * 0.68363 + math.cos(t * 1.3) * 1.55)) * 0.5 + samples[2] = (1 + math.sin(t * 1.1642 + math.cos(t * 0.33457) * 1.24)) * 0.5 + samples[3] = (1 + math.sin(t * 0.56345 + math.cos(t * 1.63) * 0.14)) * 0.5 + samples[4] = (1 + math.sin(t * 1.6245 + math.cos(t * 0.254) * 0.3)) * 0.5 + samples[5] = (1 + math.sin(t * 0.345 + math.cos(t * 0.03) * 0.6)) * 0.5 + + for i in range(6): + sx[i] = x + i * dx + sy[i] = y + h * samples[i] * 0.8 + + # Graph background + bg = nvg.linear_gradient(vg, x, y, x, y + h, nvg.rgba(0, 160, 192, 0), nvg.rgba(0, 160, 192, 64)) + nvg.begin_path(vg) + nvg.move_to(vg, sx[0], sy[0]) + for i in range(1, 6): + nvg.bezier_to(vg, sx[i - 1] + dx * 0.5, sy[i - 1], sx[i] - dx * 0.5, sy[i], sx[i], sy[i]) + nvg.line_to(vg, x + w, y + h) + nvg.line_to(vg, x, y + h) + nvg.fill_paint(vg, bg) + nvg.fill(vg) + + # Graph line + nvg.begin_path(vg) + nvg.move_to(vg, sx[0], sy[0] + 2) + for i in range(1, 6): + nvg.bezier_to(vg, sx[i - 1] + dx * 0.5, sy[i - 1] + 2, sx[i] - dx * 0.5, sy[i] + 2, sx[i], sy[i] + 2) + nvg.stroke_color(vg, nvg.rgba(0, 0, 0, 32)) + nvg.stroke_width(vg, 3.0) + nvg.stroke(vg) + + # Graph sample pos + for i in range(6): + bg = nvg.radial_gradient(vg, sx[i], sy[i] + 2, 3.0, 8.0, nvg.rgba(0, 0, 0, 32), nvg.rgba(0, 0, 0, 0)) + nvg.begin_path(vg) + nvg.rect(vg, sx[i] - 10, sy[i] - 10 + 2, 20, 20) + nvg.fill_paint(vg, bg) + nvg.fill(vg) + + nvg.begin_path(vg) + for i in range(6): + nvg.circle(vg, sx[i], sy[i], 4.0) + nvg.fill_color(vg, nvg.rgba(0, 160, 192, 255)) + nvg.fill(vg) + nvg.begin_path(vg) + for i in range(6): + nvg.circle(vg, sx[i], sy[i], 2.0) + nvg.fill_color(vg, nvg.rgba(220, 220, 220, 255)) + nvg.fill(vg) + + nvg.stroke_width(vg, 1.0) + + +def draw_spinner(vg: nvg.Context, cx: float, cy: float, r: float, t: float) -> None: + a0 = 0.0 + t * 6 + a1 = math.pi + t * 6 + r0 = r + r1 = r * 0.75 + nvg.save(vg) + nvg.begin_path(vg) + nvg.arc(vg, cx, cy, r0, a0, a1, nvg.Winding.cw.value) + nvg.arc(vg, cx, cy, r1, a1, a0, nvg.Winding.ccw.value) + nvg.close_path(vg) + ax = cx + math.cos(a0) * (r0 + r1) * 0.5 + ay = cy + math.sin(a0) * (r0 + r1) * 0.5 + bx = cx + math.cos(a1) * (r0 + r1) * 0.5 + by = cy + math.sin(a1) * (r0 + r1) * 0.5 + paint = nvg.linear_gradient(vg, ax, ay, bx, by, nvg.rgba(0, 0, 0, 0), nvg.rgba(0, 0, 0, 128)) + nvg.fill_paint(vg, paint) + nvg.fill(vg) + nvg.restore(vg) + + +def draw_thumbnails(vg: nvg.Context, x: float, y: float, w: float, h: float, images: List[int], nimages: int, t: float) -> None: + cornerRadius = 3.0 + thumb = 60.0 + arry = 30.5 + stackh = (nimages / 2) * (thumb + 10) + 10 + u = (1 + math.cos(t * 0.5)) * 0.5 + u2 = (1 - math.cos(t * 0.2)) * 0.5 + + nvg.save(vg) + #nvg.clear_state(vg) + + # Drop shadow + shadowPaint = nvg.box_gradient(vg, x, y + 4, w, h, cornerRadius * 2, 20, nvg.rgba(0, 0, 0, 128), nvg.rgba(0, 0, 0, 0)) + nvg.begin_path(vg) + nvg.rect(vg, x - 10, y - 10, w + 20, h + 30) + nvg.rounded_rect(vg, x, y, w, h, cornerRadius) + nvg.path_winding(vg, nvg.Winding.cw.value) + nvg.fill_paint(vg, shadowPaint) + nvg.fill(vg) + + # Window + nvg.begin_path(vg) + nvg.rounded_rect(vg, x, y, w, h, cornerRadius) + nvg.move_to(vg, x - 10, y + arry) + nvg.line_to(vg, x + 1, y + arry - 11) + nvg.line_to(vg, x + 1, y + arry + 11) + nvg.fill_color(vg, nvg.rgba(200, 200, 200, 255)) + nvg.fill(vg) + + nvg.save(vg) + nvg.scissor(vg, x, y, w, h) + nvg.translate(vg, 0, -(stackh - h) * u) + + dv = 1.0 / (nimages - 1) + + for i in range(nimages): + tx = x + 10 + ty = y + 10 + tx += (i % 2) * (thumb + 10) + ty += (i / 2) * (thumb + 10) + imgw, imgh = nvg.image_size(vg, images[i]) + if imgw < imgh: + iw = thumb + ih = iw * imgh / imgw + ix = 0 + iy = -(ih - thumb) * 0.5 + else: + ih = thumb + iw = ih * imgw / imgh + ix = -(iw - thumb) * 0.5 + iy = 0 + + v = i * dv + a = clamp((u2 - v) / dv, 0, 1) + + if a < 1.0: + draw_spinner(vg, tx + thumb / 2, ty + thumb / 2, thumb * 0.25, t) + + imgPaint = nvg.image_pattern(vg, tx + ix, ty + iy, iw, ih, 0.0 / 180.0 * math.pi, images[i], a) + nvg.begin_path(vg) + nvg.rounded_rect(vg, tx, ty, thumb, thumb, 5) + nvg.fill_paint(vg, imgPaint) + nvg.fill(vg) + + shadowPaint = nvg.box_gradient(vg, tx - 1, ty, thumb + 2, thumb + 2, 5, 3, nvg.rgba(0, 0, 0, 128), nvg.rgba(0, 0, 0, 0)) + nvg.begin_path(vg) + nvg.rect(vg, tx - 5, ty - 5, thumb + 10, thumb + 10) + nvg.rounded_rect(vg, tx, ty, thumb, thumb, 6) + nvg.path_winding(vg, nvg.Solidity.hole.value) + nvg.fill_paint(vg, shadowPaint) + nvg.fill(vg) + + nvg.begin_path(vg) + nvg.rounded_rect(vg, tx + 0.5, ty + 0.5, thumb - 1, thumb - 1, 4 - 0.5) + nvg.stroke_width(vg, 1.0) + nvg.stroke_color(vg, nvg.rgba(255, 255, 255, 192)) + nvg.stroke(vg) + + nvg.restore(vg) + + # Hide fades + fadePaint = nvg.linear_gradient(vg, x, y, x, y + 6, nvg.rgba(200, 200, 200, 255), nvg.rgba(200, 200, 200, 0)) + nvg.begin_path(vg) + nvg.rect(vg, x + 4, y, w - 8, 6) + nvg.fill_paint(vg, fadePaint) + nvg.fill(vg) + + fadePaint = nvg.linear_gradient(vg, x, y + h, x, y + h - 6, nvg.rgba(200, 200, 200, 255), nvg.rgba(200, 200, 200, 0)) + nvg.begin_path(vg) + nvg.rect(vg, x + 4, y + h - 6, w - 8, 6) + nvg.fill_paint(vg, fadePaint) + nvg.fill(vg) + + # Scroll bar + shadowPaint = nvg.box_gradient(vg, x + w - 12 + 1, y + 4 + 1, 8, h - 8, 3, 4, nvg.rgba(0, 0, 0, 32), nvg.rgba(0, 0, 0, 92)) + nvg.begin_path(vg) + nvg.rounded_rect(vg, x + w - 12, y + 4, 8, h - 8, 3) + nvg.fill_paint(vg, shadowPaint) + nvg.fill(vg) + + scrollh = (h / stackh) * (h - 8) + shadowPaint = nvg.box_gradient(vg, x + w - 12 - 1, y + 4 + (h - 8 - scrollh) * u - 1, 8, scrollh, 3, 4, nvg.rgba(220, 220, 220, 255), nvg.rgba(128, 128, 128, 255)) + nvg.begin_path(vg) + nvg.rounded_rect(vg, x + w - 12 + 1, y + 4 + 1 + (h - 8 - scrollh) * u, 8 - 2, scrollh - 2, 2) + nvg.fill_paint(vg, shadowPaint) + nvg.fill(vg) + + nvg.restore(vg) + + +def draw_colorwheel(vg: nvg.Context, x: float, y: float, w: float, h: float, t: float) -> None: + hue = math.sin(t * 0.12) + nvg.save(vg) + + cx = x + w * 0.5 + cy = y + h * 0.5 + w_or_h = w if w < h else h + r1 = w_or_h * 0.5 - 5.0 + r0 = r1 - 20.0 + aeps = 0.5 / r1 # half a pixel arc length in radians (2pi cancels out). + + for i in range(6): + a0 = i / 6.0 * math.pi * 2.0 - aeps + a1 = (i + 1.0) / 6.0 * math.pi * 2.0 + aeps + nvg.begin_path(vg) + nvg.arc(vg, cx, cy, r0, a0, a1, nvg.Winding.cw.value) + nvg.arc(vg, cx, cy, r1, a1, a0, nvg.Winding.ccw.value) + nvg.close_path(vg) + ax = cx + math.cos(a0) * (r0 + r1) * 0.5 + ay = cy + math.sin(a0) * (r0 + r1) * 0.5 + bx = cx + math.cos(a1) * (r0 + r1) * 0.5 + by = cy + math.sin(a1) * (r0 + r1) * 0.5 + paint = nvg.linear_gradient(vg, ax, ay, bx, by, nvg.hsla(a0 / (math.pi * 2), 1.0, 0.55, 255), nvg.hsla(a1 / (math.pi * 2), 1.0, 0.55, 255)) + nvg.fill_paint(vg, paint) + nvg.fill(vg) + + nvg.begin_path(vg) + nvg.circle(vg, cx, cy, r0 - 0.5) + nvg.circle(vg, cx, cy, r1 + 0.5) + nvg.stroke_color(vg, nvg.rgba(0, 0, 0, 64)) + nvg.stroke_width(vg, 1.0) + nvg.stroke(vg) + + # Selector + nvg.save(vg) + nvg.translate(vg, cx, cy) + nvg.rotate(vg, hue * math.pi * 2) + + # Marker on + nvg.stroke_width(vg, 2.0) + nvg.begin_path(vg) + nvg.rect(vg, r0 - 1, -3, r1 - r0 + 2, 6) + nvg.stroke_color(vg, nvg.rgba(255, 255, 255, 192)) + nvg.stroke(vg) + + paint = nvg.box_gradient(vg, r0 - 3, -5, r1 - r0 + 6, 10, 2, 4, nvg.rgba(0, 0, 0, 128), nvg.rgba(0, 0, 0, 0)) + nvg.begin_path(vg) + nvg.rect(vg, r0 - 2 - 10, -4 - 10, r1 - r0 + 4 + 20, 8 + 20) + nvg.rect(vg, r0 - 2, -4, r1 - r0 + 4, 8) + nvg.path_winding(vg, nvg.Solidity.hole.value) + nvg.fill_paint(vg, paint) + nvg.fill(vg) + + # Center triangle + r = r0 - 6 + ax = math.cos(120.0 / 180.0 * math.pi) * r + ay = math.sin(120.0 / 180.0 * math.pi) * r + bx = math.cos(-120.0 / 180.0 * math.pi) * r + by = math.sin(-120.0 / 180.0 * math.pi) * r + nvg.begin_path(vg) + nvg.move_to(vg, r, 0) + nvg.line_to(vg, ax, ay) + nvg.line_to(vg, bx, by) + nvg.close_path(vg) + paint = nvg.linear_gradient(vg, r, 0, ax, ay, nvg.hsla(hue, 1.0, 0.5, 255), nvg.rgba(255, 255, 255, 255)) + nvg.fill_paint(vg, paint) + nvg.fill(vg) + paint = nvg.linear_gradient(vg, (r + ax) * 0.5, (0 + ay) * 0.5, bx, by, nvg.rgba(0, 0, 0, 0), nvg.rgba(0, 0, 0, 255)) + nvg.fill_paint(vg, paint) + nvg.fill(vg) + nvg.stroke_color(vg, nvg.rgba(0, 0, 0, 64)) + nvg.stroke(vg) + + # Select circle on triangle + ax = math.cos(120.0 / 180.0 * math.pi) * r * 0.3 + ay = math.sin(120.0 / 180.0 * math.pi) * r * 0.4 + nvg.stroke_width(vg, 2.0) + nvg.begin_path(vg) + nvg.circle(vg, ax, ay, 5) + nvg.stroke_color(vg, nvg.rgba(255, 255, 255, 192)) + nvg.stroke(vg) + + paint = nvg.radial_gradient(vg, ax, ay, 7, 9, nvg.rgba(0, 0, 0, 64), nvg.rgba(0, 0, 0, 0)) + nvg.begin_path(vg) + nvg.rect(vg, ax - 20, ay - 20, 40, 40) + nvg.circle(vg, ax, ay, 7) + nvg.path_winding(vg, nvg.Solidity.hole.value) + nvg.fill_paint(vg, paint) + nvg.fill(vg) + + nvg.restore(vg) + + nvg.restore(vg) + + +def draw_lines(vg: nvg.Context, x: float, y: float, w: float, h: float, t: float) -> None: + pad = 5.0 + s = w / 9.0 - pad * 2 + pts = [0.0] * 4 * 2 + fx = [0.0] * 4 + fy = [0.0] * 4 + joins = [nvg.LineCap.round.value, nvg.LineCap.round.value, nvg.LineCap.bevel.value, nvg.LineCap.miter.value] + caps = [nvg.LineCap.butt.value, nvg.LineCap.round.value, nvg.LineCap.round.value, nvg.LineCap.round.value] + + nvg.save(vg) + pts[0] = -s * 0.25 + math.cos(t * 0.3) * s * 0.5 + pts[1] = math.sin(t * 0.3) * s * 0.5 + pts[2] = -s * 0.25 + pts[3] = 0 + pts[4] = s * 0.25 + pts[5] = 0 + pts[6] = s * 0.25 + math.cos(-t * 0.3) * s * 0.5 + pts[7] = math.sin(-t * 0.3) * s * 0.5 + + for i in range(4): + for j in range(4): + fx = x + s * 0.5 + (i * 9 + j) / 16.0 * w + pad + fy = y - s * 0.5 + pad + + nvg.line_cap(vg, caps[i]) + nvg.line_join(vg, joins[j]) + + nvg.stroke_width(vg, s * 0.3) + nvg.stroke_color(vg, nvg.rgba(0, 0, 0, 160)) + nvg.begin_path(vg) + nvg.move_to(vg, fx + pts[0], fy + pts[1]) + nvg.line_to(vg, fx + pts[2], fy + pts[3]) + nvg.line_to(vg, fx + pts[4], fy + pts[5]) + nvg.line_to(vg, fx + pts[6], fy + pts[7]) + nvg.stroke(vg) + + nvg.line_cap(vg, nvg.LineCap.butt.value) + nvg.line_join(vg, nvg.LineCap.bevel.value) + + nvg.stroke_width(vg, 1.0) + nvg.stroke_color(vg, nvg.rgba(0, 192, 255, 255)) + nvg.begin_path(vg) + nvg.move_to(vg, fx + pts[0], fy + pts[1]) + nvg.line_to(vg, fx + pts[2], fy + pts[3]) + nvg.line_to(vg, fx + pts[4], fy + pts[5]) + nvg.line_to(vg, fx + pts[6], fy + pts[7]) + nvg.stroke(vg) + + nvg.restore(vg) + + +def load_demo_data(vg: nvg.Context, data: DemoData) -> int: + for i in range(12): + file = f"nanovg_demo_images/image{i+1}.jpg" + file_asset_path = hello_imgui.asset_file_full_path(file) + data.images[i] = nvg.create_image(vg, file_asset_path, 0) + if data.images[i] == 0: + print(f"Could not load {file}") + return -1 + + data.fontIcons = nvg.create_font(vg, "icons", hello_imgui.asset_file_full_path("fonts/entypo.ttf")) + if data.fontIcons == -1: + print("Could not add font icons.") + return -1 + + data.fontNormal = nvg.create_font(vg, "sans", hello_imgui.asset_file_full_path("fonts/Roboto/Roboto-Regular.ttf")) + if data.fontNormal == -1: + print("Could not add font italic.") + return -1 + + data.fontBold = nvg.create_font(vg, "sans-bold", hello_imgui.asset_file_full_path("fonts/Roboto/Roboto-Bold.ttf")) + if data.fontBold == -1: + print("Could not add font bold.") + return -1 + + data.fontEmoji = nvg.create_font(vg, "emoji", hello_imgui.asset_file_full_path("fonts/NotoEmoji-Regular.ttf")) + if data.fontEmoji == -1: + print("Could not add font emoji.") + return -1 + + nvg.add_fallback_font_id(vg, data.fontNormal, data.fontEmoji) + nvg.add_fallback_font_id(vg, data.fontBold, data.fontEmoji) + + return 0 + + +def free_demo_data(vg: nvg.Context, data: DemoData) -> None: + for i in range(12): + nvg.delete_image(vg, data.images[i]) + + +def draw_paragraph(vg: nvg.Context, x: float, y: float, width: float, height: float, mx: float, my: float) -> None: + lnum = 0 + gutter = 0 + gx = 0 + gy = 0 + text = "This is longer chunk of text.\n \n Would have used lorem ipsum but she was busy jumping over the lazy dog with the fox and all the men who came to the aid of the party.🎉" + hoverText = "Hover your mouse over the text to see calculated caret position." + # boxText = "Testing\nsome multiline\ntext." + + nvg.save(vg) + + nvg.font_size(vg, 18.0) + nvg.font_face(vg, "sans") + nvg.text_align(vg, nvg.Align.align_left.value | nvg.Align.align_top.value) + metrics = nvg.text_metrics(vg) + lineh = metrics.lineh + + # The text break API can be used to fill a large buffer of rows, + # or to iterate over the text just few lines (or just one) at a time. + # The "next" variable of the last returned item tells where to continue. + start = text + + # The rest is not ported, the demo manipulate too many C pointers + text_rows = nvg.text_break_lines(vg, start, width) + for i in range(len(text_rows)): + row = text_rows[i] + hit = mx > x and mx < (x + width) and my >= y and my < (y + lineh) + + nvg.begin_path(vg) + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 16)) + nvg.rect(vg, x, y, row.width, lineh) + nvg.fill(vg) + + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 255)) + nvg.text(vg, x, y, row.row_text) + + if hit: + caretx = x if mx < x + row.width / 2 else x + row.width + px = x + glyphs = nvg.text_glyph_positions(vg, x, y, row.row_text) + for j in range(len(glyphs)): + x0 = glyphs[j].x + x1 = glyphs[j + 1].x if j + 1 < len(glyphs) else x + row.width + gx = x0 * 0.3 + x1 * 0.7 + if mx >= px and mx < gx: + caretx = glyphs[j].x + px = gx + + nvg.begin_path(vg) + nvg.fill_color(vg, nvg.rgba(255, 192, 0, 255)) + nvg.rect(vg, caretx, y, 1, lineh) + nvg.fill(vg) + + gutter = lnum + 1 + gx = x - 10 + gy = y + lineh / 2 + + lnum += 1 + y += lineh + + # Keep going + #start = row.next + + if gutter: + txt = str(gutter) + nvg.font_size(vg, 12.0) + nvg.text_align(vg, nvg.Align.align_right.value | nvg.Align.align_middle.value) + + bounds, _ = nvg.text_bounds(vg, gx, gy, txt) + + nvg.begin_path(vg) + nvg.fill_color(vg, nvg.rgba(255, 192, 0, 255)) + nvg.rounded_rect(vg, math.floor(bounds[0]) - 4, math.floor(bounds[1]) - 2, math.floor(bounds[2] - bounds[0]) + 8, math.floor(bounds[3] - bounds[1]) + 4, (math.floor(bounds[3] - bounds[1]) + 4) / 2 - 1) + nvg.fill(vg) + + nvg.fill_color(vg, nvg.rgba(32, 32, 32, 255)) + nvg.text(vg, gx, gy, txt) + + y += 20.0 + + nvg.font_size(vg, 11.0) + nvg.text_align(vg, nvg.Align.align_left.value | nvg.Align.align_top.value) + nvg.text_line_height(vg, 1.2) + + bounds = nvg.text_box_bounds(vg, x, y, 150, hoverText) + + # Fade the tooltip out when close to it. + gx = clamp(mx, bounds[0], bounds[2]) - mx + gy = clamp(my, bounds[1], bounds[3]) - my + a = math.sqrt(gx * gx + gy * gy) / 30.0 + a = clamp(a, 0, 1) + nvg.global_alpha(vg, a) + + nvg.begin_path(vg) + nvg.fill_color(vg, nvg.rgba(220, 220, 220, 255)) + nvg.rounded_rect(vg, bounds[0] - 2, bounds[1] - 2, math.floor(bounds[2] - bounds[0]) + 4, math.floor(bounds[3] - bounds[1]) + 4, 3) + px = math.floor((bounds[2] + bounds[0]) / 2) + nvg.move_to(vg, px, bounds[1] - 10) + nvg.line_to(vg, px + 7, bounds[1] + 1) + nvg.line_to(vg, px - 7, bounds[1] + 1) + nvg.fill(vg) + + nvg.fill_color(vg, nvg.rgba(0, 0, 0, 220)) + nvg.text_box(vg, x, y, 150, hoverText) + + nvg.restore(vg) + +def draw_widths(vg: nvg.Context, x: float, y: float, width: float) -> None: + nvg.save(vg) + + nvg.stroke_color(vg, nvg.rgba(0, 0, 0, 255)) + + for i in range(20): + w = (i + 0.5) * 0.1 + nvg.stroke_width(vg, w) + nvg.begin_path(vg) + nvg.move_to(vg, x, y) + nvg.line_to(vg, x + width, y + width * 0.3) + nvg.stroke(vg) + + nvg.restore(vg) + + +def draw_caps(vg: nvg.Context, x: float, y: float, width: float) -> None: + caps = [nvg.LineCap.butt.value, nvg.LineCap.round.value, nvg.LineCap.square.value] + lineWidth = 8.0 + + nvg.save(vg) + + nvg.begin_path(vg) + nvg.rect(vg, x - lineWidth / 2, y, width + lineWidth, 40) + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 32)) + nvg.fill(vg) + + nvg.begin_path(vg) + nvg.rect(vg, x, y, width, 40) + nvg.fill_color(vg, nvg.rgba(255, 255, 255, 32)) + nvg.fill(vg) + + nvg.stroke_width(vg, lineWidth) + for i in range(3): + nvg.line_cap(vg, caps[i]) + nvg.stroke_color(vg, nvg.rgba(0, 0, 0, 255)) + nvg.begin_path(vg) + nvg.move_to(vg, x, y + i * 10 + 5) + nvg.line_to(vg, x + width, y + i * 10 + 5) + nvg.stroke(vg) + + nvg.restore(vg) + + +def draw_scissor(vg: nvg.Context, x: float, y: float, t: float) -> None: + nvg.save(vg) + + # Draw first rect and set scissor to it's area. + nvg.translate(vg, x, y) + nvg.rotate(vg, nvg.deg_to_rad(5)) + nvg.begin_path(vg) + nvg.rect(vg, -20, -20, 60, 40) + nvg.fill_color(vg, nvg.rgba(255, 0, 0, 255)) + nvg.fill(vg) + nvg.scissor(vg, -20, -20, 60, 40) + + # Draw second rectangle with offset and rotation. + nvg.translate(vg, 40, 0) + nvg.rotate(vg, t) + + # Draw the intended second rectangle without any scissoring. + nvg.begin_path(vg) + nvg.rect(vg, -20, -10, 60, 30) + nvg.fill_color(vg, nvg.rgba(255, 128, 0, 64)) + nvg.fill(vg) + + # Draw second rectangle with combined scissoring. + nvg.begin_path(vg) + nvg.rect(vg, -20, -10, 60, 30) + nvg.fill_color(vg, nvg.rgba(255, 128, 0, 255)) + nvg.fill(vg) + + nvg.restore(vg) + + +def render_demo(vg: nvg.Context, mx: float, my: float, width: float, height: float, t: float, blowup: bool, data: DemoData) -> None: + draw_eyes(vg, width - 250, 50, 150, 100, mx, my, t) + draw_paragraph(vg, width - 450, 50, 150, 100, mx, my) + draw_graph(vg, 0, height / 2, width, height / 2, t) + draw_colorwheel(vg, width - 300, height - 300, 250.0, 250.0, t) + + # Line joints + draw_lines(vg, 120, height - 50, 600, 50, t) + + # Line caps + draw_widths(vg, 10, 50, 30) + + # Line caps + draw_caps(vg, 10, 300, 30) + + draw_scissor(vg, 50, height - 80, t) + + nvg.save(vg) + if blowup: + nvg.rotate(vg, math.sin(t * 0.3) * 5.0 / 180.0 * math.pi) + nvg.scale(vg, 2.0, 2.0) + + # Widgets + draw_window(vg, "Widgets `n Stuff", 50, 50, 300, 400) + x = 60 + y = 95 + draw_search_box(vg, "Search", x, y, 280, 25) + y += 40 + draw_drop_down(vg, "Effects", x, y, 280, 28) + popy = y + 14 + y += 45 + + # Form + draw_label(vg, "Login", x, y, 280, 20) + y += 25 + draw_edit_box(vg, "Email", x, y, 280, 28) + y += 35 + draw_edit_box(vg, "Password", x, y, 280, 28) + y += 38 + draw_check_box(vg, "Remember me", x, y, 140, 28) + draw_button(vg, ICON_LOGIN, "Sign in", x + 138, y, 140, 28, nvg.rgba(0, 96, 128, 255)) + y += 45 + + # Slider + draw_label(vg, "Diameter", x, y, 280, 20) + y += 25 + draw_edit_box_num(vg, "123.00", "px", x + 180, y, 100, 28) + draw_slider(vg, 0.4, x, y, 170, 28) + y += 55 + + draw_button(vg, ICON_TRASH, "Delete", x, y, 160, 28, nvg.rgba(128, 16, 8, 255)) + draw_button(vg, "", "Cancel", x + 170, y, 110, 28, nvg.rgba(0, 0, 0, 0)) + + # Thumbnails box + draw_thumbnails(vg, 365, popy - 30, 160, 300, data.images, 12, t) + + nvg.restore(vg) diff --git a/bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_heart.py b/bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_heart.py index 5c101db8..22a48002 100644 --- a/bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_heart.py +++ b/bindings/imgui_bundle/demos_python/demos_nanovg/demo_nanovg_heart.py @@ -60,7 +60,7 @@ def draw_nano_vg_label(vg: nvg.Context, width: float, height: float): nvg.save(vg) nvg.rotate(vg, -0.1) nvg.fill_color(vg, nvg.rgba(255, 100, 100, 255)) - nvg.text(vg, 0.5 * width, 0.9 * height, "NanoVG", None) + nvg.text(vg, 0.5 * width, 0.9 * height, "NanoVG") nvg.restore(vg) diff --git a/bindings/imgui_bundle/nanovg.pyi b/bindings/imgui_bundle/nanovg.pyi index b1dafc9a..48b877ea 100644 --- a/bindings/imgui_bundle/nanovg.pyi +++ b/bindings/imgui_bundle/nanovg.pyi @@ -1,6 +1,6 @@ import enum import numpy as np -from typing import Any, Callable +from typing import Any, Callable, Tuple, List from imgui_bundle import ImVec4 from imgui_bundle.imgui import ImTextureID @@ -11,6 +11,8 @@ Context = OpaquePointer UChar = int # a value between 0 and 255 NvgDrawingFunction = Callable[[Context, int, int], Any] +Bounds = Tuple[float, float, float, float] + class Color: r: float g: float @@ -332,7 +334,7 @@ def rgb(r: UChar, g: UChar, b: UChar) -> Color: pass # NVGcolor nvgRGBf(float r, float g, float b); /* original C++ signature */ -def rg_bf(r: float, g: float, b: float) -> Color: +def rgb_f(r: float, g: float, b: float) -> Color: """ Returns a color value from red, green, blue values. Alpha will be set to 1.0.""" pass @@ -657,10 +659,6 @@ def update_image(ctx: Context, image: int, data: UChar) -> None: """ Updates image data specified by image handle.""" pass -# void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h); /* original C++ signature */ -def image_size(ctx: Context, image: int, w: int, h: int) -> None: - """ Returns the dimensions of a created image.""" - pass # void nvgDeleteImage(NVGcontext* ctx, int image); /* original C++ signature */ def delete_image(ctx: Context, image: int) -> None: @@ -1036,20 +1034,8 @@ def font_blur(ctx: Context, blur: float) -> None: """ Sets the blur of current text style.""" pass -# void nvgTextLetterSpacing(NVGcontext* ctx, float spacing); /* original C++ signature */ -def text_letter_spacing(ctx: Context, spacing: float) -> None: - """ Sets the letter spacing of current text style.""" - pass -# void nvgTextLineHeight(NVGcontext* ctx, float lineHeight); /* original C++ signature */ -def text_line_height(ctx: Context, line_height: float) -> None: - """ Sets the proportional line height of current text style. The line height is specified as multiple of font size.""" - pass -# void nvgTextAlign(NVGcontext* ctx, int align); /* original C++ signature */ -def text_align(ctx: Context, align: int) -> None: - """ Sets the text align of current text style, see NVGalign for options.""" - pass # void nvgFontFaceId(NVGcontext* ctx, int font); /* original C++ signature */ def font_face_id(ctx: Context, font: int) -> None: @@ -1061,99 +1047,12 @@ def font_face(ctx: Context, font: str) -> None: """ Sets the font face based on specified name of current text style.""" pass -# float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end); /* original C++ signature */ -def text(ctx: Context, x: float, y: float, string: str, end: str) -> float: - """ Draws text string at specified location. If end is specified only the sub-string up to the end is drawn.""" - pass -# void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end); /* original C++ signature */ -def text_box( - ctx: Context, - x: float, - y: float, - break_row_width: float, - string: str, - end: str - ) -> None: - """ Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn. - White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. - Words longer than the max width are slit at nearest character (i.e. no hyphenation). - """ - pass -# float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds); /* original C++ signature */ -def text_bounds( - ctx: Context, - x: float, - y: float, - string: str, - end: str, - bounds: float - ) -> float: - """ Measures the specified text string. Parameter bounds should be a pointer to float[4], - if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] - Returns the horizontal advance of the measured text (i.e. where the next character should drawn). - Measured values are returned in local coordinate space. - """ - pass -# void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds); /* original C++ signature */ -def text_box_bounds( - ctx: Context, - x: float, - y: float, - break_row_width: float, - string: str, - end: str, - bounds: float - ) -> None: - """ Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], - if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] - Measured values are returned in local coordinate space. - """ - pass -# int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions); /* original C++ signature */ -def text_glyph_positions( - ctx: Context, - x: float, - y: float, - string: str, - end: str, - positions: GlyphPosition, - max_positions: int - ) -> int: - """ Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used. - Measured values are returned in local coordinate space. - """ - pass -# void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh); /* original C++ signature */ -def text_metrics( - ctx: Context, - ascender: float, - descender: float, - lineh: float - ) -> None: - """ Returns the vertical metrics based on the current text style. - Measured values are returned in local coordinate space. - """ - pass -# int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows); /* original C++ signature */ -def text_break_lines( - ctx: Context, - string: str, - end: str, - break_row_width: float, - rows: TextRow, - max_rows: int - ) -> int: - """ Breaks the specified text into lines. If end is specified only the sub-string will be used. - White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. - Words longer than the max width are slit at nearest character (i.e. no hyphenation). - """ - pass class Texture(enum.Enum): """ @@ -1392,4 +1291,148 @@ class nvg_imgui: # Proxy class that introduces typings for the *submodule* nvg_ # #################### #################### + +#################### #################### + + + +# C++ Wrappers to NanoVG text functions, to simplify python bindings + +# float nvgcpp_Text(NVGcontext* ctx, float x, float y, const std::string& text); /* original C++ signature */ +def text(ctx: Context, x: float, y: float, text: str) -> float: + """ Draws text string at specified location. If end is specified only the sub-string up to the end is drawn.""" + pass + +# void nvgcpp_TextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const std::string& text); /* original C++ signature */ +def text_box( + ctx: Context, + x: float, + y: float, + break_row_width: float, + text: str + ) -> None: + """ Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn. + White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. + Words longer than the max width are split at nearest character (i.e. no hyphenation). + """ + pass + + + +# std::tuple nvgcpp_TextBounds(NVGcontext* ctx, float x, float y, const std::string& text); /* original C++ signature */ +def text_bounds( + ctx: Context, + x: float, + y: float, + text: str + ) -> Tuple[Bounds, float]: + """ Measures the specified text string. Parameter bounds should be a pointer to float[4], + if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] + Returns the bounds + the horizontal advance of the measured text (i.e. where the next character should drawn) + Measured values are returned in local coordinate space. + """ + pass + +# Bounds nvgcpp_TextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const std::string& text); /* original C++ signature */ +def text_box_bounds( + ctx: Context, + x: float, + y: float, + break_row_width: float, + text: str + ) -> Bounds: + """ Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], + if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] + Measured values are returned in local coordinate space. + """ + pass + +# std::vector nvgcpp_TextGlyphPositions(NVGcontext* ctx, float x, float y, const std::string& text); /* original C++ signature */ +def text_glyph_positions( + ctx: Context, + x: float, + y: float, + text: str + ) -> List[GlyphPosition]: + """ Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used. + Measured values are returned in local coordinate space. + """ + pass + + +class TextMetricsData: + # float ascender; /* original C++ signature */ + ascender: float + # float descender; /* original C++ signature */ + descender: float + # float lineh; /* original C++ signature */ + lineh: float + # TextMetricsData(float ascender = float(), float descender = float(), float lineh = float()); /* original C++ signature */ + def __init__( + self, + ascender: float = float(), + descender: float = float(), + lineh: float = float() + ) -> None: + """Auto-generated default constructor with named params""" + pass + +# TextMetricsData nvgcpp_TextMetrics(NVGcontext* ctx); /* original C++ signature */ +def text_metrics(ctx: Context) -> TextMetricsData: + """ Returns the vertical metrics based on the current text style. + Measured values are returned in local coordinate space. + """ + pass + + +class TextRowSimple: + # std::string row_text; /* original C++ signature */ + row_text: str + # float width; /* original C++ signature */ + width: float # Logical width of the row. + # float minx, /* original C++ signature */ + minx: float # Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. + # maxx; /* original C++ signature */ + maxx: float # Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. + # NVGtextRowSimple(std::string row_text = std::string(), float width = float(), float minx = float(), float maxx = float()); /* original C++ signature */ + def __init__( + self, + row_text: str = "", + width: float = float(), + minx: float = float(), + maxx: float = float() + ) -> None: + """Auto-generated default constructor with named params""" + pass + + +# std::vector nvgcpp_TextBreakLines(NVGcontext* ctx, const std::string& text, float breakRowWidth); /* original C++ signature */ +def text_break_lines( + ctx: Context, + text: str, + break_row_width: float + ) -> List[TextRowSimple]: + """ Breaks the specified text into lines. If end is specified only the sub-string will be used. + White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. + Words longer than the max width are split at nearest character (i.e. no hyphenation). + """ + pass + +# void nvgcpp_TextAlign(NVGcontext* ctx, int align); /* original C++ signature */ +def text_align(ctx: Context, align: int) -> None: + """ Sets the text align of current text style, see NVGalign for options.""" + pass + +# void nvgcpp_TextLineHeight(NVGcontext* ctx, float lineHeight); /* original C++ signature */ +def text_line_height(ctx: Context, line_height: float) -> None: + """ Sets the proportional line height of current text style. The line height is specified as multiple of font size.""" + pass + + +# std::tuple nvgcpp_ImageSize(NVGcontext* ctx, int image); /* original C++ signature */ +def image_size(ctx: Context, image: int) -> Tuple[int, int]: + """ Returns the dimensions of a created image.""" + pass +#################### #################### + # // Autogenerated code end! diff --git a/external/nanovg/bindings/generate_nanovg.py b/external/nanovg/bindings/generate_nanovg.py index ce86f1e5..6c8197cb 100644 --- a/external/nanovg/bindings/generate_nanovg.py +++ b/external/nanovg/bindings/generate_nanovg.py @@ -1,5 +1,6 @@ # Part of ImGui Bundle - MIT License - Copyright (c) 2022-2023 Pascal Thomet - https://github.com/pthom/imgui_bundle import os +import string import litgen @@ -19,11 +20,16 @@ def main(): options.original_signature_flag_show = True options.type_replacements.add_last_replacement("unsigned char", "UChar") options.var_names_replacements.add_last_replacement("^NVG_", "") + options.function_names_replacements.add_last_replacement("^nvgcpp_", "") options.function_names_replacements.add_last_replacement("^nvg", "") options.function_names_replacements.add_last_replacement("^RGBAf$", "rgba_f") + options.function_names_replacements.add_last_replacement("^RGBf$", "rgb_f") options.class_exclude_by_name__regex = "^NVGcolor$" # contains a union... - for letter in "abcdefghijklmnopqrstuvwxyz": + # The entire nvgText API is very C style, and needs adaptations + options.fn_exclude_by_name__regex = r"^nvgText|^nvgImageSize$" + + for letter in string.ascii_lowercase: options.type_replacements.add_last_replacement( "NVG" + letter, letter.upper() ) @@ -31,6 +37,7 @@ def main(): generator = litgen.LitgenGenerator(options) generator.process_cpp_file(THIS_DIR + "/../nanovg/src/nanovg.h") generator.process_cpp_file(THIS_DIR + "/../nvg_imgui/nvg_imgui.h") + generator.process_cpp_file(THIS_DIR + "/../nvg_imgui/nvg_cpp_text.h") generator.write_generated_code(output_cpp_pydef_file, output_stub_pyi_file) diff --git a/external/nanovg/bindings/pybind_nanovg.cpp b/external/nanovg/bindings/pybind_nanovg.cpp index f7f888c8..07cba541 100644 --- a/external/nanovg/bindings/pybind_nanovg.cpp +++ b/external/nanovg/bindings/pybind_nanovg.cpp @@ -6,6 +6,8 @@ #include "nanovg.h" #include "nvg_imgui/nvg_imgui.h" +#include "nvg_imgui/nvg_cpp_text.h" + namespace py = pybind11; @@ -278,7 +280,7 @@ void py_init_module_nanovg(py::module& m) py::arg("r"), py::arg("g"), py::arg("b"), "Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0)."); - m.def("rg_bf", + m.def("rgb_f", nvgRGBf, py::arg("r"), py::arg("g"), py::arg("b"), "Returns a color value from red, green, blue values. Alpha will be set to 1.0."); @@ -499,11 +501,6 @@ void py_init_module_nanovg(py::module& m) py::arg("ctx"), py::arg("image"), py::arg("data"), "Updates image data specified by image handle."); - m.def("image_size", - nvgImageSize, - py::arg("ctx"), py::arg("image"), py::arg("w"), py::arg("h"), - "Returns the dimensions of a created image."); - m.def("delete_image", nvgDeleteImage, py::arg("ctx"), py::arg("image"), @@ -679,21 +676,6 @@ void py_init_module_nanovg(py::module& m) py::arg("ctx"), py::arg("blur"), "Sets the blur of current text style."); - m.def("text_letter_spacing", - nvgTextLetterSpacing, - py::arg("ctx"), py::arg("spacing"), - "Sets the letter spacing of current text style."); - - m.def("text_line_height", - nvgTextLineHeight, - py::arg("ctx"), py::arg("line_height"), - "Sets the proportional line height of current text style. The line height is specified as multiple of font size."); - - m.def("text_align", - nvgTextAlign, - py::arg("ctx"), py::arg("align"), - "Sets the text align of current text style, see NVGalign for options."); - m.def("font_face_id", nvgFontFaceId, py::arg("ctx"), py::arg("font"), @@ -704,41 +686,6 @@ void py_init_module_nanovg(py::module& m) py::arg("ctx"), py::arg("font"), "Sets the font face based on specified name of current text style."); - m.def("text", - nvgText, - py::arg("ctx"), py::arg("x"), py::arg("y"), py::arg("string"), py::arg("end"), - "Draws text string at specified location. If end is specified only the sub-string up to the end is drawn."); - - m.def("text_box", - nvgTextBox, - py::arg("ctx"), py::arg("x"), py::arg("y"), py::arg("break_row_width"), py::arg("string"), py::arg("end"), - " Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn.\n White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.\n Words longer than the max width are slit at nearest character (i.e. no hyphenation)."); - - m.def("text_bounds", - nvgTextBounds, - py::arg("ctx"), py::arg("x"), py::arg("y"), py::arg("string"), py::arg("end"), py::arg("bounds"), - " Measures the specified text string. Parameter bounds should be a pointer to float[4],\n if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]\n Returns the horizontal advance of the measured text (i.e. where the next character should drawn).\n Measured values are returned in local coordinate space."); - - m.def("text_box_bounds", - nvgTextBoxBounds, - py::arg("ctx"), py::arg("x"), py::arg("y"), py::arg("break_row_width"), py::arg("string"), py::arg("end"), py::arg("bounds"), - " Measures the specified multi-text string. Parameter bounds should be a pointer to float[4],\n if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]\n Measured values are returned in local coordinate space."); - - m.def("text_glyph_positions", - nvgTextGlyphPositions, - py::arg("ctx"), py::arg("x"), py::arg("y"), py::arg("string"), py::arg("end"), py::arg("positions"), py::arg("max_positions"), - " Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used.\n Measured values are returned in local coordinate space."); - - m.def("text_metrics", - nvgTextMetrics, - py::arg("ctx"), py::arg("ascender"), py::arg("descender"), py::arg("lineh"), - " Returns the vertical metrics based on the current text style.\n Measured values are returned in local coordinate space."); - - m.def("text_break_lines", - nvgTextBreakLines, - py::arg("ctx"), py::arg("string"), py::arg("end"), py::arg("break_row_width"), py::arg("rows"), py::arg("max_rows"), - " Breaks the specified text into lines. If end is specified only the sub-string will be used.\n White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.\n Words longer than the max width are slit at nearest character (i.e. no hyphenation)."); - py::enum_(m, "Texture", py::arithmetic(), "\n Internal Render API\n") .value("texture_alpha", NVG_TEXTURE_ALPHA, "") @@ -910,6 +857,103 @@ void py_init_module_nanovg(py::module& m) } // //////////////////// //////////////////// + + //////////////////// //////////////////// + m.def("text", + nvgcpp_Text, + py::arg("ctx"), py::arg("x"), py::arg("y"), py::arg("text"), + "Draws text string at specified location. If end is specified only the sub-string up to the end is drawn."); + + m.def("text_box", + nvgcpp_TextBox, + py::arg("ctx"), py::arg("x"), py::arg("y"), py::arg("break_row_width"), py::arg("text"), + " Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn.\n White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.\n Words longer than the max width are split at nearest character (i.e. no hyphenation)."); + + m.def("text_bounds", + nvgcpp_TextBounds, + py::arg("ctx"), py::arg("x"), py::arg("y"), py::arg("text"), + " Measures the specified text string. Parameter bounds should be a pointer to float[4],\n if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]\n Returns the bounds + the horizontal advance of the measured text (i.e. where the next character should drawn)\n Measured values are returned in local coordinate space."); + + m.def("text_box_bounds", + nvgcpp_TextBoxBounds, + py::arg("ctx"), py::arg("x"), py::arg("y"), py::arg("break_row_width"), py::arg("text"), + " Measures the specified multi-text string. Parameter bounds should be a pointer to float[4],\n if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax]\n Measured values are returned in local coordinate space."); + + m.def("text_glyph_positions", + nvgcpp_TextGlyphPositions, + py::arg("ctx"), py::arg("x"), py::arg("y"), py::arg("text"), + " Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used.\n Measured values are returned in local coordinate space."); + + + auto pyClassTextMetricsData = + py::class_ + (m, "TextMetricsData", "") + .def(py::init<>([]( + float ascender = float(), float descender = float(), float lineh = float()) + { + auto r = std::make_unique(); + r->ascender = ascender; + r->descender = descender; + r->lineh = lineh; + return r; + }) + , py::arg("ascender") = float(), py::arg("descender") = float(), py::arg("lineh") = float() + ) + .def_readwrite("ascender", &TextMetricsData::ascender, "") + .def_readwrite("descender", &TextMetricsData::descender, "") + .def_readwrite("lineh", &TextMetricsData::lineh, "") + ; + + + m.def("text_metrics", + nvgcpp_TextMetrics, + py::arg("ctx"), + " Returns the vertical metrics based on the current text style.\n Measured values are returned in local coordinate space."); + + + auto pyClassNVGtextRowSimple = + py::class_ + (m, "TextRowSimple", "") + .def(py::init<>([]( + std::string row_text = std::string(), float width = float(), float minx = float(), float maxx = float()) + { + auto r = std::make_unique(); + r->row_text = row_text; + r->width = width; + r->minx = minx; + r->maxx = maxx; + return r; + }) + , py::arg("row_text") = std::string(), py::arg("width") = float(), py::arg("minx") = float(), py::arg("maxx") = float() + ) + .def_readwrite("row_text", &NVGtextRowSimple::row_text, "") + .def_readwrite("width", &NVGtextRowSimple::width, "Logical width of the row.") + .def_readwrite("minx", &NVGtextRowSimple::minx, "Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.") + .def_readwrite("maxx", &NVGtextRowSimple::maxx, "Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending.") + ; + + + m.def("text_break_lines", + nvgcpp_TextBreakLines, + py::arg("ctx"), py::arg("text"), py::arg("break_row_width"), + " Breaks the specified text into lines. If end is specified only the sub-string will be used.\n White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.\n Words longer than the max width are split at nearest character (i.e. no hyphenation)."); + + m.def("text_align", + nvgcpp_TextAlign, + py::arg("ctx"), py::arg("align"), + "Sets the text align of current text style, see NVGalign for options."); + + m.def("text_line_height", + nvgcpp_TextLineHeight, + py::arg("ctx"), py::arg("line_height"), + "Sets the proportional line height of current text style. The line height is specified as multiple of font size."); + + m.def("image_size", + nvgcpp_ImageSize, + py::arg("ctx"), py::arg("image"), + "Returns the dimensions of a created image."); + //////////////////// //////////////////// + // // Autogenerated code end // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! AUTOGENERATED CODE END !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! } diff --git a/external/nanovg/nvg_imgui/nvg_cpp_text.cpp b/external/nanovg/nvg_imgui/nvg_cpp_text.cpp new file mode 100644 index 00000000..507c996d --- /dev/null +++ b/external/nanovg/nvg_imgui/nvg_cpp_text.cpp @@ -0,0 +1,80 @@ +#include "nvg_cpp_text.h" +#include "nanovg.h" + + +float nvgcpp_Text(NVGcontext* ctx, float x, float y, const std::string& text) +{ + return nvgText(ctx, x, y, text.c_str(), nullptr); +} + +void nvgcpp_TextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const std::string& text) +{ + nvgTextBox(ctx, x, y, breakRowWidth, text.c_str(), nullptr); +} + +std::tuple nvgcpp_TextBounds(NVGcontext* ctx, float x, float y, const std::string& text) +{ + Bounds bounds; + float advance = nvgTextBounds(ctx, x, y, text.c_str(), nullptr, bounds.data()); + return std::make_tuple(bounds, advance); +} + +Bounds nvgcpp_TextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const std::string& text) +{ + Bounds bounds; + nvgTextBoxBounds(ctx, x, y, breakRowWidth, text.c_str(), nullptr, bounds.data()); + return bounds; +} + + +std::vector nvgcpp_TextGlyphPositions(NVGcontext* ctx, float x, float y, const std::string& text) +{ + std::vector positions(text.size()); + nvgTextGlyphPositions(ctx, x, y, text.c_str(), nullptr, positions.data(), (int)positions.size()); + return positions; +} + +TextMetricsData nvgcpp_TextMetrics(NVGcontext* ctx) +{ + TextMetricsData data; + nvgTextMetrics(ctx, &data.ascender, &data.descender, &data.lineh); + return data; +} + +void nvgcpp_TextLineHeight(NVGcontext* ctx, float lineHeight) +{ + nvgTextLineHeight(ctx, lineHeight); +} + + +std::vector nvgcpp_TextBreakLines(NVGcontext* ctx, const std::string& text, float breakRowWidth) +{ + constexpr int maxRows = 1000; + NVGtextRow rows[maxRows]; + int nbRows = nvgTextBreakLines(ctx, text.c_str(), nullptr, breakRowWidth, rows, maxRows); + std::vector rowsVector; + rowsVector.reserve(nbRows); + for (int i = 0; i < nbRows; ++i) + { + NVGtextRowSimple row; + row.width = rows[i].width; + row.minx = rows[i].minx; + row.maxx = rows[i].maxx; + row.row_text = std::string(rows[i].start, rows[i].end); + rowsVector.push_back(row); + } + return rowsVector; +} + +void nvgcpp_TextAlign(NVGcontext* ctx, int align) +{ + nvgTextAlign(ctx, align); +} + + +std::tuple nvgcpp_ImageSize(NVGcontext* ctx, int image) +{ + int w, h; + nvgImageSize(ctx, image, &w, &h); + return std::make_tuple(w, h); +} diff --git a/external/nanovg/nvg_imgui/nvg_cpp_text.h b/external/nanovg/nvg_imgui/nvg_cpp_text.h new file mode 100644 index 00000000..d3d6a6b5 --- /dev/null +++ b/external/nanovg/nvg_imgui/nvg_cpp_text.h @@ -0,0 +1,71 @@ +#pragma once + +#include "nanovg.h" +#include +#include +#include +#include + + +// C++ Wrappers to NanoVG text functions, to simplify python bindings + +// Draws text string at specified location. If end is specified only the sub-string up to the end is drawn. +float nvgcpp_Text(NVGcontext* ctx, float x, float y, const std::string& text); + +// Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn. +// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. +// Words longer than the max width are split at nearest character (i.e. no hyphenation). +void nvgcpp_TextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const std::string& text); + + +using Bounds = std::array; + +// Measures the specified text string. Parameter bounds should be a pointer to float[4], +// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] +// Returns the bounds + the horizontal advance of the measured text (i.e. where the next character should drawn) +// Measured values are returned in local coordinate space. +std::tuple nvgcpp_TextBounds(NVGcontext* ctx, float x, float y, const std::string& text); + +// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], +// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] +// Measured values are returned in local coordinate space. +Bounds nvgcpp_TextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const std::string& text); + +// Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used. +// Measured values are returned in local coordinate space. +std::vector nvgcpp_TextGlyphPositions(NVGcontext* ctx, float x, float y, const std::string& text); + + +struct TextMetricsData +{ + float ascender; + float descender; + float lineh; +}; + +// Returns the vertical metrics based on the current text style. +// Measured values are returned in local coordinate space. +TextMetricsData nvgcpp_TextMetrics(NVGcontext* ctx); + + +struct NVGtextRowSimple { + std::string row_text; + float width; // Logical width of the row. + float minx, maxx; // Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. +}; + + +// Breaks the specified text into lines. If end is specified only the sub-string will be used. +// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. +// Words longer than the max width are split at nearest character (i.e. no hyphenation). +std::vector nvgcpp_TextBreakLines(NVGcontext* ctx, const std::string& text, float breakRowWidth); + +// Sets the text align of current text style, see NVGalign for options. +void nvgcpp_TextAlign(NVGcontext* ctx, int align); + +// Sets the proportional line height of current text style. The line height is specified as multiple of font size. +void nvgcpp_TextLineHeight(NVGcontext* ctx, float lineHeight); + + +// Returns the dimensions of a created image. +std::tuple nvgcpp_ImageSize(NVGcontext* ctx, int image);