Skip to content

Commit 0403983

Browse files
tobitegePWagner1
andauthored
2461 v100 (feat) Add KryptonCalcInput edit control (#2462)
* KryptonCalcButton initial commit * Enhance KryptonCalcButton with default glyph and custom dropdown icon support * KCalcButton switched to input control with new properties and event handling for improved functionality * Rename KryptonCalcButton to KryptonCalcInput and update associated resources and references * Implement ESC key handling to close popup in KryptonCalcInput * Rename to KryptonCalcInput. Replace visual popup with context menu approach like datepicker. * Added changelog entry --------- Co-authored-by: Peter Wagner <[email protected]>
1 parent 8b9625c commit 0403983

File tree

14 files changed

+2200
-9
lines changed

14 files changed

+2200
-9
lines changed

Documents/Changelog/Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
====
44

55
## 2025-11-xx - Build 2511 (V10 - alpha) - November 2025
6+
* Resolved [#2461](https://github.com/Krypton-Suite/Standard-Toolkit/issues/2461), (feat) Add `KryptonCalcInput` edit+dropdown control.
67
* Resolved [#2480](https://github.com/Krypton-Suite/Standard-Toolkit/issues/2480). `KryptonForm`'s 'InternalPanel' designer issues
78
* Resolved [#2512](https://github.com/Krypton-Suite/Standard-Toolkit/issues/2512), Added borders with straight corners (`LinearBorder2`) and adjusted the colors in the `KryptonRibbon` in the `Microsoft365` themes. Adjusted the design of the `RibbonQATButton`
89
* Implemented [#2503](https://github.com/Krypton-Suite/Standard-Toolkit/issues/2503), Add the ability to create zip files for binaries

Scripts/make_calc_icon.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Python 3.10+
2+
# Creates a 16x16 24-bit BMP depicting a simple calculator.
3+
# Default output: Source\Krypton Components\Krypton.Toolkit\Resources\KryptonCalcInput.bmp
4+
5+
from __future__ import annotations
6+
7+
from pathlib import Path
8+
from typing import List, Tuple
9+
import sys
10+
11+
# Image size
12+
WIDTH = 16
13+
HEIGHT = 16
14+
15+
# Colors (B, G, R) for BMP storage convenience
16+
BGR = Tuple[int, int, int]
17+
18+
MAGENTA: BGR = (255, 0, 255) # transparent key background for legacy use
19+
GRAY: BGR = (200, 200, 200) # body
20+
DARK: BGR = (30, 30, 30) # outline
21+
DISPLAY: BGR = (170, 220, 180) # display area
22+
BTN: BGR = (230, 230, 230) # buttons
23+
BTN_EDGE: BGR = (60, 60, 60) # button outline
24+
EQUALS: BGR = (200, 210, 250) # equals button
25+
26+
27+
def clamp8(v: int) -> int:
28+
if v < 0:
29+
return 0
30+
if v > 255:
31+
return 255
32+
return v
33+
34+
35+
def new_canvas(color: BGR) -> List[List[BGR]]:
36+
return [[color for _ in range(WIDTH)] for _ in range(HEIGHT)]
37+
38+
39+
def set_px(img: List[List[BGR]], x: int, y: int, color: BGR) -> None:
40+
if 0 <= x < WIDTH and 0 <= y < HEIGHT:
41+
b, g, r = color
42+
img[y][x] = (clamp8(b), clamp8(g), clamp8(r))
43+
44+
45+
def fill_rect(img: List[List[BGR]], x0: int, y0: int, x1: int, y1: int, color: BGR) -> None:
46+
if x1 < x0:
47+
x0, x1 = x1, x0
48+
if y1 < y0:
49+
y0, y1 = y1, y0
50+
for y in range(max(0, y0), min(HEIGHT - 1, y1) + 1):
51+
for x in range(max(0, x0), min(WIDTH - 1, x1) + 1):
52+
set_px(img, x, y, color)
53+
54+
55+
def draw_rect(img: List[List[BGR]], x0: int, y0: int, x1: int, y1: int, color: BGR) -> None:
56+
if x1 < x0:
57+
x0, x1 = x1, x0
58+
if y1 < y0:
59+
y0, y1 = y1, y0
60+
for x in range(x0, x1 + 1):
61+
set_px(img, x, y0, color)
62+
set_px(img, x, y1, color)
63+
for y in range(y0, y1 + 1):
64+
set_px(img, x0, y, color)
65+
set_px(img, x1, y, color)
66+
67+
68+
def draw_calculator() -> List[List[BGR]]:
69+
img = new_canvas(MAGENTA)
70+
71+
# Body
72+
fill_rect(img, 1, 1, 14, 14, GRAY)
73+
draw_rect(img, 1, 1, 14, 14, DARK)
74+
75+
# Display
76+
fill_rect(img, 3, 3, 12, 6, DISPLAY)
77+
draw_rect(img, 3, 3, 12, 6, DARK)
78+
79+
# Buttons 2 rows of 3 + wide equals bottom
80+
buttons = [
81+
(3, 7, 5, 9), (7, 7, 9, 9), (11, 7, 13, 9),
82+
(3, 10, 5, 12), (7, 10, 9, 12), (11, 10, 13, 12),
83+
]
84+
for (x0, y0, x1, y1) in buttons:
85+
fill_rect(img, x0, y0, x1, y1, BTN)
86+
draw_rect(img, x0, y0, x1, y1, BTN_EDGE)
87+
88+
# Equals wide
89+
fill_rect(img, 3, 13, 13, 14, EQUALS)
90+
draw_rect(img, 3, 13, 13, 14, BTN_EDGE)
91+
92+
return img
93+
94+
95+
def to_24bit_bottom_up(img: List[List[BGR]]) -> bytes:
96+
# Each row is WIDTH*3 bytes; for width=16 -> 48 bytes (already multiple of 4, so no padding).
97+
out = bytearray()
98+
for y in range(HEIGHT - 1, -1, -1):
99+
for x in range(WIDTH):
100+
b, g, r = img[y][x]
101+
out.extend((b, g, r))
102+
return bytes(out)
103+
104+
105+
def write_bmp_24(path: Path, img: List[List[BGR]]) -> None:
106+
pixel_data = to_24bit_bottom_up(img)
107+
file_header_size = 14
108+
info_header_size = 40
109+
off_bits = file_header_size + info_header_size
110+
size_image = len(pixel_data)
111+
file_size = off_bits + size_image
112+
113+
# BITMAPFILEHEADER
114+
bfType = b"BM"
115+
bfSize = file_size.to_bytes(4, "little")
116+
bfReserved1 = (0).to_bytes(2, "little")
117+
bfReserved2 = (0).to_bytes(2, "little")
118+
bfOffBits = off_bits.to_bytes(4, "little")
119+
file_header = bfType + bfSize + bfReserved1 + bfReserved2 + bfOffBits
120+
121+
# BITMAPINFOHEADER
122+
biSize = info_header_size.to_bytes(4, "little")
123+
biWidth = WIDTH.to_bytes(4, "little", signed=True)
124+
biHeight = HEIGHT.to_bytes(4, "little", signed=True)
125+
biPlanes = (1).to_bytes(2, "little")
126+
biBitCount = (24).to_bytes(2, "little")
127+
biCompression = (0).to_bytes(4, "little") # BI_RGB
128+
biSizeImage = size_image.to_bytes(4, "little")
129+
biXPelsPerMeter = (0).to_bytes(4, "little")
130+
biYPelsPerMeter = (0).to_bytes(4, "little")
131+
biClrUsed = (0).to_bytes(4, "little")
132+
biClrImportant = (0).to_bytes(4, "little")
133+
info_header = (
134+
biSize + biWidth + biHeight + biPlanes + biBitCount + biCompression +
135+
biSizeImage + biXPelsPerMeter + biYPelsPerMeter + biClrUsed + biClrImportant
136+
)
137+
138+
path.parent.mkdir(parents=True, exist_ok=True)
139+
with path.open("wb") as f:
140+
f.write(file_header)
141+
f.write(info_header)
142+
f.write(pixel_data)
143+
144+
145+
def default_output_path() -> Path:
146+
# Scripts/make_calc_icon.py -> repo root -> Source/.../Resources/KryptonCalcInput.bmp
147+
repo_root = Path(__file__).resolve().parents[1]
148+
return repo_root / "Source" / "Krypton Components" / "Krypton.Toolkit" / "Resources" / "KryptonCalcInput.bmp"
149+
150+
151+
def main(argv: list[str]) -> int:
152+
out_path = Path(argv[1]) if len(argv) > 1 else default_output_path()
153+
img = draw_calculator()
154+
write_bmp_24(out_path, img)
155+
print(f"Wrote 16x16 BMP to {out_path}")
156+
return 0
157+
158+
159+
if __name__ == "__main__":
160+
raise SystemExit(main(sys.argv))

0 commit comments

Comments
 (0)