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 <AcfboyU@outlook.com>
This commit is contained in:
Acfboy 2026-08-01 23:00:02 +08:00 committed by Alan C. Assis
parent edb4d26663
commit d79e503612
4 changed files with 598 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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

510
examples/nanoxcalc/nxcalc.c Normal file
View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#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);
}
}