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..d43a74128 --- /dev/null +++ b/examples/nanoxcalc/nxcalc.c @@ -0,0 +1,356 @@ +/* nxcalc - Simple graphical calculator for Microwindows / Nano-X + * + * Inspired by nxclock example (Greg Haerr). + * + * Run inside an environment where the Nano-X server is available. + */ + +#include +#include +#include +#include +#include +#include "nano-X.h" + +#define WIN_W 160 +#define WIN_H 200 +#define DISP_H 35 + +static GR_WINDOW_ID win; +static GR_GC_ID gc_text, gc_button, gc_button_press, 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 +}; + +#define COLS 4 +#define ROWS 5 + +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); + +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, c, 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, 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, c, idx, tw, th, 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); + } +}