From d79e503612c7d25a606f2f8b3d7fc1cd6f5a85b3 Mon Sep 17 00:00:00 2001 From: Acfboy Date: Sat, 1 Aug 2026 23:00:02 +0800 Subject: [PATCH] examples/nanoxcalc: add Nano-X calculator example * Port the nxcalc demo from Microwindows * Connects to the Nano-X server started by examples/nanoxterm * Requires CONFIG_LIBC_FLOATINGPOINT for the result formatting Signed-off-by: Acfboy --- examples/nanoxcalc/Kconfig | 28 ++ examples/nanoxcalc/Make.defs | 25 ++ examples/nanoxcalc/Makefile | 35 +++ examples/nanoxcalc/nxcalc.c | 510 +++++++++++++++++++++++++++++++++++ 4 files changed, 598 insertions(+) create mode 100644 examples/nanoxcalc/Kconfig create mode 100644 examples/nanoxcalc/Make.defs create mode 100644 examples/nanoxcalc/Makefile create mode 100644 examples/nanoxcalc/nxcalc.c diff --git a/examples/nanoxcalc/Kconfig b/examples/nanoxcalc/Kconfig new file mode 100644 index 000000000..5b2fa079c --- /dev/null +++ b/examples/nanoxcalc/Kconfig @@ -0,0 +1,28 @@ +config EXAMPLES_NANOXCALC + tristate "Nano-X calculator example" + default n + depends on MICROWINDOWS_NANOX && LIBC_FLOATINGPOINT + ---help--- + Enable the Nano-X calculator example application. It is a + port of the nxcalc demo from Microwindows which connects to + the Nano-X server started by examples/nanoxterm and shows + a calculator keypad operated by the mouse or keyboard. + +if EXAMPLES_NANOXCALC + +config EXAMPLES_NANOXCALC_PROGNAME + string "Program name" + default "nanoxcalc" + ---help--- + This is the name of the program that will be used when the NSH ELF + program is installed. + +config EXAMPLES_NANOXCALC_PRIORITY + int "Nano-X calculator task priority" + default 100 + +config EXAMPLES_NANOXCALC_STACKSIZE + int "Nano-X calculator stack size" + default 16384 + +endif diff --git a/examples/nanoxcalc/Make.defs b/examples/nanoxcalc/Make.defs new file mode 100644 index 000000000..c30810158 --- /dev/null +++ b/examples/nanoxcalc/Make.defs @@ -0,0 +1,25 @@ +############################################################################ +# apps/examples/nanoxcalc/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_EXAMPLES_NANOXCALC),) +CONFIGURED_APPS += $(APPDIR)/examples/nanoxcalc +endif diff --git a/examples/nanoxcalc/Makefile b/examples/nanoxcalc/Makefile new file mode 100644 index 000000000..7a703a398 --- /dev/null +++ b/examples/nanoxcalc/Makefile @@ -0,0 +1,35 @@ +############################################################################ +# apps/examples/nanoxcalc/Makefile +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +PROGNAME = $(CONFIG_EXAMPLES_NANOXCALC_PROGNAME) +PRIORITY = $(CONFIG_EXAMPLES_NANOXCALC_PRIORITY) +STACKSIZE = $(CONFIG_EXAMPLES_NANOXCALC_STACKSIZE) +MODULE = $(CONFIG_EXAMPLES_NANOXCALC) + +# Port of the upstream nxcalc demo (src/demos/nanox/nxcalc.c) +MAINSRC = nxcalc.c +CFLAGS += -Wno-undef + + +include $(APPDIR)/Application.mk diff --git a/examples/nanoxcalc/nxcalc.c b/examples/nanoxcalc/nxcalc.c new file mode 100644 index 000000000..8a7936dcf --- /dev/null +++ b/examples/nanoxcalc/nxcalc.c @@ -0,0 +1,510 @@ +/**************************************************************************** + * apps/examples/nanoxcalc/nxcalc.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * nxcalc - Simple graphical calculator for Microwindows / Nano-X + * + * Inspired by the nxclock example (Greg Haerr). + * + * Run inside an environment where the Nano-X server is available. + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ +#include +#include +#include +#include +#include +#include "nano-X.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ +#define WIN_W 160 +#define WIN_H 200 +#define DISP_H 35 +#define COLS 4 +#define ROWS 5 +static GR_WINDOW_ID win; +static GR_GC_ID gc_text; +static GR_GC_ID gc_button; +static GR_GC_ID gc_button_press; +static GR_GC_ID gc_border; + +/* calculator state */ + +static char display[128]; +static double acc = 0.0; +static char pending_op = 0; +static int entering = 0; +static int dot_entered = 0; + +/* button layout */ + +static char *btn_labels[] = +{ + "C", "<-", "+/-", "/", "7", "8", "9", "*", + "4", "5", "6", "-", "1", "2", "3", "+", + "0", ".", "=", NULL +}; + +static GR_RECT btn_rects[ROWS][COLS]; + +/* function prototypes */ + +static void redraw(void); +static void press_button(const char *label); +static void compute_equal(void); +static void apply_pending(double val); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static double display_to_double(void) +{ + if (strlen(display) == 0) + { + return 0.0; + } + + return strtod(display, NULL); +} + +static void set_display_from_double(double v) +{ + char buf[128]; + double iv; + + if (fabs(v) < 1e-12) + { + v = 0.0; + } + + if (modf(v, &iv) == 0.0) + { + snprintf(buf, sizeof(buf), "%.0f", v); + } + else + { + snprintf(buf, sizeof(buf), "%.10g", v); + } + + strncpy(display, buf, sizeof(display) - 1); + display[sizeof(display) - 1] = '\0'; +} + +/* ========================= MAIN ========================= */ + +int main(int argc, char **argv) +{ + GR_EVENT event; + int r; + int c; + int idx; + + if (GrOpen() < 0) + { + fprintf(stderr, "nxcalc: cannot open Nano-X\n"); + return 1; + } + + win = GrNewWindowEx(GR_WM_PROPS_APPWINDOW, "nxcalc", GR_ROOT_WINDOW_ID, 10, + 10, WIN_W, WIN_H, GrGetSysColor(GR_COLOR_WINDOW)); + + GrSelectEvents(win, + GR_EVENT_MASK_EXPOSURE | GR_EVENT_MASK_BUTTON_DOWN | + GR_EVENT_MASK_BUTTON_UP | GR_EVENT_MASK_CLOSE_REQ); + + gc_text = GrNewGC(); + gc_button = GrNewGC(); + gc_button_press = GrNewGC(); + gc_border = GrNewGC(); + + GrSetGCForeground(gc_text, GrGetSysColor(GR_COLOR_WINDOWTEXT)); + GrSetGCBackground(gc_text, GrGetSysColor(GR_COLOR_WINDOW)); + + GrSetGCForeground(gc_button, GrGetSysColor(GR_COLOR_BTNFACE)); + GrSetGCBackground(gc_button, GrGetSysColor(GR_COLOR_WINDOW)); + + GrSetGCForeground(gc_button_press, GrGetSysColor(GR_COLOR_BTNSHADOW)); + GrSetGCBackground(gc_button_press, GrGetSysColor(GR_COLOR_WINDOW)); + + GrSetGCForeground(gc_border, GrGetSysColor(GR_COLOR_BTNTEXT)); + GrSetGCBackground(gc_border, GrGetSysColor(GR_COLOR_WINDOW)); + + /* init state */ + + display[0] = '\0'; + acc = 0.0; + pending_op = 0; + entering = 0; + dot_entered = 0; + + /* layout buttons */ + + int grid_x = 12; + int grid_y = DISP_H + 10; + int grid_w = WIN_W - 20; + int grid_h = WIN_H - DISP_H - 20; + int cell_w = grid_w / COLS; + int cell_h = grid_h / ROWS; + + idx = 0; + for (r = 0; r < ROWS; r++) + { + for (c = 0; c < COLS; c++) + { + int x = grid_x + c * cell_w; + int y = grid_y + r * cell_h; + btn_rects[r][c].x = x; + btn_rects[r][c].y = y; + btn_rects[r][c].width = cell_w - 6; + btn_rects[r][c].height = cell_h - 6; + idx++; + } + } + + GrMapWindow(win); + set_display_from_double(0.0); + redraw(); + + while (1) + { + GrGetNextEvent(&event); + switch (event.type) + { + case GR_EVENT_TYPE_EXPOSURE: + { + redraw(); + break; + } + + case GR_EVENT_TYPE_BUTTON_DOWN: + { + int mx = event.button.x; + int my = event.button.y; + int found = 0; + for (r = 0; r < ROWS && !found; r++) + { + for (c = 0; c < COLS && !found; c++) + { + GR_RECT *rc = &btn_rects[r][c]; + if (mx >= rc->x && mx <= rc->x + rc->width && + my >= rc->y && my <= rc->y + rc->height) + { + GrFillRect(win, gc_button_press, rc->x, rc->y, + rc->width, rc->height); + found = 1; + } + } + } + break; + } + + case GR_EVENT_TYPE_BUTTON_UP: + { + int mx = event.button.x; + int my = event.button.y; + int found = 0; + for (r = 0; r < ROWS && !found; r++) + { + for (c = 0; c < COLS && !found; c++) + { + GR_RECT *rc = &btn_rects[r][c]; + if (mx >= rc->x && mx <= rc->x + rc->width && + my >= rc->y && my <= rc->y + rc->height) + { + idx = r * COLS + c; + const char *lbl = (idx < + (int)(sizeof(btn_labels) / + sizeof(btn_labels[0]))? + btn_labels[idx] : NULL); + if (lbl) + { + press_button(lbl); + redraw(); + } + + found = 1; + } + } + } + break; + } + + case GR_EVENT_TYPE_CLOSE_REQ: + { + GrClose(); + return 0; + } + } + } + + return 0; +} + +/* ========================= UI DRAWING ========================= */ + +static void redraw(void) +{ + int r; + int c; + int idx; + int tw; + int th; + int tb; + + /* display area */ + + GrSetGCForeground(gc_button, GrGetSysColor(GR_COLOR_WINDOW)); + GrFillRect(win, gc_button, 5, 5, WIN_W - 10, DISP_H - 10); + GrSetGCForeground(gc_border, GrGetSysColor(GR_COLOR_BTNSHADOW)); + GrRect(win, gc_border, 5, 5, WIN_W - 11, DISP_H - 10); + + /* display text (right aligned) */ + + GrGetGCTextSize(gc_text, display, strlen(display), GR_TFTOP, &tw, + &th, &tb); + + int x = WIN_W - 12 - tw; + if (x < 10) + { + x = 10; + } + + GrText(win, gc_text, x, 5 + (DISP_H / 2) - 10, display, strlen(display), + GR_TFTOP); + + /* draw buttons */ + + idx = 0; + GrSetGCForeground(gc_button, GrGetSysColor(GR_COLOR_BTNFACE)); + for (r = 0; r < ROWS; r++) + { + for (c = 0; c < COLS; c++) + { + GR_RECT *rc = &btn_rects[r][c]; + char *lbl = (idx < + (int)(sizeof(btn_labels) / + sizeof(btn_labels[0]))? btn_labels[idx] : NULL); + + GrSetGCForeground(gc_button, GrGetSysColor(GR_COLOR_WINDOW)); + GrFillRect(win, gc_button, rc->x, rc->y, rc->width, rc->height); + GrSetGCForeground(gc_border, GrGetSysColor(GR_COLOR_BTNSHADOW)); + GrRect(win, gc_border, rc->x, rc->y, rc->width, rc->height); + + if (lbl) + { + GrGetGCTextSize(gc_text, lbl, strlen(lbl), GR_TFTOP, &tw, &th, + &tb); + GrText(win, gc_text, rc->x + rc->width / 2 - tw / 2, + rc->y + rc->height / 2 - 6, lbl, strlen(lbl), GR_TFTOP); + } + + idx++; + } + } + + GrFlush(); +} + +/* ========================= LOGIC ========================= */ + +static void press_button(const char *label) +{ + char tmp[128]; + double val; + + if (strcmp(label, "C") == 0) + { + display[0] = '\0'; + acc = 0.0; + pending_op = 0; + entering = 0; + dot_entered = 0; + set_display_from_double(0.0); + return; + } + + if (strcmp(label, "<-") == 0) + { + int l = strlen(display); + if (l > 0) + { + if (display[l - 1] == '.') + { + dot_entered = 0; + } + + display[l - 1] = '\0'; + if (strlen(display) == 0) + { + set_display_from_double(0.0); + } + } + + entering = (strlen(display) > 0); + return; + } + + if (strcmp(label, "+/-") == 0) + { + if (display[0] == '-') + { + memmove(display, display + 1, strlen(display)); + } + else + { + if (strlen(display) == 0 || strcmp(display, "0") == 0) + { + return; + } + + snprintf(tmp, sizeof(tmp), "-%s", display); + strncpy(display, tmp, sizeof(display) - 1); + } + + return; + } + + if (strcmp(label, "+") == 0 || strcmp(label, "-") == 0 || strcmp(label, + "*") == 0 || strcmp(label, "/") == 0) + { + val = display_to_double(); + if (pending_op == 0) + { + acc = val; + } + else + { + apply_pending(val); + } + + pending_op = label[0]; + entering = 0; + dot_entered = 0; + set_display_from_double(acc); + return; + } + + if (strcmp(label, "=") == 0) + { + compute_equal(); + return; + } + + if (strcmp(label, ".") == 0) + { + if (!dot_entered) + { + if (!entering) + { + strcpy(display, "0."); + entering = 1; + } + else + { + strncat(display, ".", sizeof(display) - strlen(display) - 1); + } + + dot_entered = 1; + } + + return; + } + + if (strlen(label) == 1 && isdigit((unsigned char)label[0])) + { + if (!entering) + { + display[0] = label[0]; + display[1] = '\0'; + entering = 1; + } + else + { + if (strcmp(display, "0") == 0 && label[0] != '0' && !dot_entered) + { + display[0] = label[0]; + display[1] = '\0'; + } + else if (strlen(display) < sizeof(display) - 2) + { + strncat(display, label, 1); + } + } + + return; + } +} + +static void apply_pending(double val) +{ + if (pending_op == '+') + { + acc = acc + val; + } + else if (pending_op == '-') + { + acc = acc - val; + } + else if (pending_op == '*') + { + acc = acc * val; + } + else if (pending_op == '/') + { + if (val == 0.0) + { + strncpy(display, "Error", sizeof(display) - 1); + display[sizeof(display) - 1] = '\0'; + pending_op = 0; + entering = 0; + dot_entered = 0; + redraw(); + return; + } + else + { + acc = acc / val; + } + } +} + +static void compute_equal(void) +{ + double val = display_to_double(); + + if (pending_op != 0) + { + apply_pending(val); + pending_op = 0; + set_display_from_double(acc); + entering = 0; + dot_entered = 0; + } + else + { + set_display_from_double(val); + } +}