한번에 두개나 주는군. 그런데 먼가 안끌린다!?

'게임 > 오리진&스팀&유플레이' 카테고리의 다른 글
| 에픽 유출은 역시 구라였군 (0) | 2025.12.20 |
|---|---|
| 오랫만에 지르기 (0) | 2025.11.29 |
| 에픽 무료게임 (0) | 2025.11.14 |
| 윈도우 11에서 JASF 실행 실패 (0) | 2025.11.02 |
| 스팀 단상 (0) | 2025.10.05 |
한번에 두개나 주는군. 그런데 먼가 안끌린다!?

| 에픽 유출은 역시 구라였군 (0) | 2025.12.20 |
|---|---|
| 오랫만에 지르기 (0) | 2025.11.29 |
| 에픽 무료게임 (0) | 2025.11.14 |
| 윈도우 11에서 JASF 실행 실패 (0) | 2025.11.02 |
| 스팀 단상 (0) | 2025.10.05 |
액정은 뜯을수가 없어서 포기하고, 뒷면 전체를 보면
평범한(?) esp32-wroom-32 , 터치ic, sd 카드, rgb led, usb 시리얼 칩 그리고 ldo가 보인다.

esp32-wroom-32
xpt2046 터치 ic
rgb led

스피커 단자도 있는데 esp32의 dac 기능이 있었나?
AMS1117 LDO 2개
LTK8002D 오디오 앰프. 읭?

2채널 DAC가 있다고 하니 그걸 사용하면 될 것 같긴한데 일단 스피커와 앰프는 1채널만 해둔 듯?
[링크 : https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/dac.html]
[링크 : https://blog.naver.com/mapes_khkim/222284886951]
CH430C usb to serial

윈도우에서 보니 드라이버가 없는지 안 잡혀서

sparkfun에서 받아서 설치하니 잡힌다.
micro USB 나 USB-C 양쪽모두 시리얼 포트가 연결되어 있다.

[링크 : https://learn.sparkfun.com/tutorials/how-to-install-ch340-drivers/all#windows-710]
시리얼 포트를 115200bps로 연결하고 reset을 눌러보니 친숙한(?)
esp32 부트로더가 보이는 듯.

같이 준 케이스에 먼가 써있어서 구글 렌즈의 힘을 빌어보니
터치기능이 있는 7789라.. st7789 LCD 드라이버를 쓴걸려나?
![]() |
![]() |
맞다면.. 실험적으로는 65fps까지 나온다는 건데 오호.. 1.5만원에 사온거 치고는 좋은 액정이군(!)
| ESP32 LCDs performance report 1. ST7796 4-wire SPI 16fps 2. ST7796 8-bit 34fps 3. ILI9488 4-wire SPI 11fps 4. ILI9488 8-bit 34fps 5. ST7789 4-wire SPI 65fps 6. ST7789 8-bit 5xfps(weird) 7. ILI9341 4-wire SPI 65fps 8. GC9A01 8xfps 9. ST7789 240x240 8xfps |
[링크 : https://www.facebook.com/groups/797613180807626/posts/1048617505707191/]
내껀 v3 인 듯? (usb-c랑 micro usb가 있음)
| ESP32-2432S028R は 3つのバージョンがあります。(R=XPT2046 抵抗膜タッチセンス) 1. ESP32-2432S028R v1 (ILI9341 SPI):初期。U4 ICの設計ミス。 2. ESP32-2432S028R v2 (ILI9341 SPI):U4 IC Remove。External Pin Assign change. 3. ESP32-2432S028R v3 (ST7789 SPI, MODE 3):A type with two USB ports.:YD2USB |
[링크 : https://macsbug.wordpress.com/2022/08/17/esp32-2432s028/]
| esp32 와 spi lcd 성능 비..교? (0) | 2026.01.12 |
|---|---|
| esp32 c6 zigbee thread matter (0) | 2026.01.10 |
| esp32 benchmark (0) | 2025.11.18 |
| esp32 lvgl (0) | 2025.11.15 |
| esp32 계열 정리 (0) | 2025.07.08 |
qt widget 에서 qml을 다루거나
qml 에서 c++로 기존 socket 등을 쓴다거나 하는걸 찾는 중
[링크 : https://doc.qt.io/qt-6/qtqml-cppintegration-interactqmlfromcpp.html]
[링크 : https://doc.qt.io/archives/qt-5.15/qtqml-cppintegration-topic.html]
[링크 : https://forum.qt.io/topic/87143/udp-and-tcp-sockets-with-qml]
[링크 : https://wiki.qt.io/QWidget-in-QML/ja]
[링크 : https://qt-dev.com/board.php?board=qnaboard&page=3&category=1&command=body&no=810]
| qt quick websocket (0) | 2026.01.14 |
|---|---|
| qt qml loader (0) | 2026.01.14 |
| qstackedwidget, qstackedlayout (0) | 2026.01.13 |
| qt quick 예제 calqlatr 코드분석 (0) | 2026.01.13 |
| qt qml view (0) | 2026.01.13 |
오랫만에 쓰려니 다 까먹어서 다시 조사
빌드
-g 옵션을 통해 디버깅 심볼을 넣어준다.
| $ gcc -g tt.c |
이거 안하면 소스코드가 안나옴
| Reading symbols from ./a.out... (No debugging symbols found in ./a.out) (gdb) l No symbol table is loaded. Use the "file" command. |
gdb 실행 (인자 없이)
| $ gdb ./a.out $ gdb (gdb) file ./a.out |
gdb 실행 (인자 필요시)
| $ gdb --args ./a.out arg1 arg2 |
브레이크 포인트 설정 및 소스 보기
소스는 list (혹은 소문자 l) 과 라인수를 넣고
브레이크 포인트는 b로 추가한다. b 뒤에는 라인수 혹은 함수 명을 넣으면 된다.
브레이크 포인트 목록 확인은 info b를 통해 가능하다.
clear 를 통해 b 명령을 통해 추가하는 것과 정반대로 삭제가 가능하고
delete(소문자 d) 명령은 info b의 num 을 이용하여 삭제한다.
| (gdb) l 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void main() 5 { 6 int *arr = NULL; 7 arr = (int*)malloc(10 * sizeof(int)); 8 int idx = 0; 9 for(idx = 0; idx < 10000; idx++) 10 arr[idx] = idx; (gdb) l 10 5 { 6 int *arr = NULL; 7 arr = (int*)malloc(10 * sizeof(int)); 8 int idx = 0; 9 for(idx = 0; idx < 10000; idx++) 10 arr[idx] = idx; 11 12 // iprintf("before\n"); 13 // fflush(stdout); 14 (gdb) l 14 9 for(idx = 0; idx < 10000; idx++) 10 arr[idx] = idx; 11 12 // iprintf("before\n"); 13 // fflush(stdout); 14 15 free(arr); 16 17 printf("after\n"); 18 fflush(stdout); (gdb) b 15 Breakpoint 1 at 0x1201: file t2.c, line 15. (gdb) b No default breakpoint address now. (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000001201 in main at t2.c:15 (gdb) b main Breakpoint 2 at 0x11b5: file t2.c, line 6. (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000001201 in main at t2.c:15 2 breakpoint keep y 0x00000000000011b5 in main at t2.c:6 (gdb) d Delete all breakpoints? (y or n) n (gdb) clear main Deleted breakpoint 2 (gdb) d 1 (gdb) info b No breakpoints or watchpoints. (gdb) b main Breakpoint 3 at 0x11b5: file t2.c, line 6. (gdb) b 15 Breakpoint 4 at 0x1201: file t2.c, line 15. (gdb) b 10 if idx = 200 Breakpoint 5 at 0x11db: file t2.c, line 10. (gdb) info b Num Type Disp Enb Address What 3 breakpoint keep y 0x00000000000011b5 in main at t2.c:6 4 breakpoint keep y 0x0000000000001201 in main at t2.c:15 5 breakpoint keep y 0x00000000000011db in main at t2.c:10 stop only if idx = 200 |
[링크 : https://dining-developer.tistory.com/13]
디버깅 시작, 디버깅 제어
run을 통해 시작하고 인자가 필요할땐
(gdb) r 1 2 3
처럼 인자를 공백으로 띄워서 넣어준다.
c는 break 포인트 까지 실행(continue)
print 를 이용하여 변수 값을 출력할수 있다.
r을 눌러 실행하나 main() 함수에서 한번 브레이크 걸리고
c를 눌러 진행하지만 10 라인에 브레이크가 걸려있어 한번 멈추고
c를 눌러 진행하지만 조건부 브레이크에 의해 idx가 200인 경우에 멈춘다.
| (gdb) r Starting program: /home/minimonk/work/src/malloc/a.out [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 3, main () at t2.c:6 6 int *arr = NULL; (gdb) c Continuing. Breakpoint 5, main () at t2.c:10 10 arr[idx] = idx; (gdb) c Continuing. Breakpoint 5, main () at t2.c:10 10 arr[idx] = idx; (gdb) print idx $1 = 200 |
next는 한줄 실행 (함수 진입 x)
step은 한줄 실행 (함수 진입)
finish는 함수 종료까지 실행(함수에서 벗어날때 까지)
| gdb attach (0) | 2025.11.11 |
|---|---|
| gdbserver taget (0) | 2023.07.19 |
| gdb conditional break (0) | 2023.07.19 |
| gdb 디버깅 타겟을 인자와 함께 실행하기 (0) | 2022.10.17 |
| gdb break (0) | 2021.04.09 |
비슷한걸 만들어 보려고 하는데 영 안되는데 신기한 옵션을 찾아서 글 써봄
의도적으로 heap을 넘기는 코드를 작성하고 free() 시에 정말 segmentation fault 가 뜨나 해보는데
| $ cat t2.c #include <stdio.h> #include <stdlib.h> void main() { int *arr = NULL; arr = (int*)malloc(10 * sizeof(int)); int idx = 0; for(idx = 0; idx < 10000; idx++) arr[idx] = idx; printf("before\n"); fflush(stdout); free(arr); printf("after\n"); fflush(stdout); } |
아쉽게도 free가 아니라 arr[idx] 에서 범위를 넘어서 에러가 발생함
| $ gcc t2.c -g $ ./a.out malloc(): corrupted top size 중지됨 (코어 덤프됨) |
fsanitize 라는 플래그를 주면 좀더 잡아 준다는데 컴파일 타임이 아니라 런타임에 작동한다.
해당 플래그를 추가하면 빌드된 용량이 증가한다.
| $ gcc t2.c -g -fsanitize=address $ ls -al 합계 40 -rwxrwxr-x 1 minimonk minimonk 23536 1월 16 10:33 a.out -rw-rw-r-- 1 minimonk minimonk 271 1월 16 10:30 t2.c $ gcc t2.c -g $ ls -al 합계 36 -rwxrwxr-x 1 minimonk minimonk 18704 1월 16 10:35 a.out -rw-rw-r-- 1 minimonk minimonk 271 1월 16 10:30 t2.c |
[링크 :https://k0n9.tistory.com/entry/AddressSanitizer]
[링크 : https://stackoverflow.com/questions/58262749/how-to-use-gcc-with-fsanitize-address]
실행해서 터트리면 아래와 같이 먼가 나오는데, 엄청 컬러풀하게 터진다.
눈에 들어오는건 summay 항목의 heap-buffer-overflow
특이한게 배열 loop 돌다 터지는게 아니라 다 돌고 나서 free 가려다가 터진다. 신기하네
| $ gcc t2.c -g -fsanitize=address $ ./a.out ================================================================= ==2713847==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x504000000038 at pc 0x609d568b42e0 bp 0x7ffdaa34e820 sp 0x7ffdaa34e810 WRITE of size 4 at 0x504000000038 thread T0 #0 0x609d568b42df in main /home/minimonk/work/src/malloc/t2.c:9 #1 0x7cc2e8429d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 #2 0x7cc2e8429e3f in __libc_start_main_impl ../csu/libc-start.c:392 #3 0x609d568b41a4 in _start (/home/minimonk/work/src/malloc/a.out+0x11a4) 0x504000000038 is located 0 bytes to the right of 40-byte region [0x504000000010,0x504000000038) allocated by thread T0 here: #0 0x7cc2e88b4887 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145 #1 0x609d568b4286 in main /home/minimonk/work/src/malloc/t2.c:7 #2 0x7cc2e8429d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 SUMMARY: AddressSanitizer: heap-buffer-overflow /home/minimonk/work/src/malloc/t2.c:9 in main Shadow bytes around the buggy address: 0x0a087fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0a087fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0a087fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0a087fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0a087fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0a087fff8000: fa fa 00 00 00 00 00[fa]fa fa fa fa fa fa fa fa 0x0a087fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0a087fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0a087fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0a087fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0a087fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==2713847==ABORTING ![]() |
idx 값을 보면 10000번 돌았는데
printf 하려고 하면 바로 malloc(): corrupted top size 하면서 터진다.
| $ gdb ./a.out GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1 Copyright (C) 2022 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./a.out... (gdb) l 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void main() 5 { 6 int *arr = NULL; 7 arr = (int*)malloc(10 * sizeof(int)); 8 int idx = 0; 9 for(idx = 0; idx < 10000; idx++) 10 arr[idx] = idx; (gdb) l 11 6 int *arr = NULL; 7 arr = (int*)malloc(10 * sizeof(int)); 8 int idx = 0; 9 for(idx = 0; idx < 10000; idx++) 10 arr[idx] = idx; 11 12 printf("before\n"); 13 fflush(stdout); 14 15 free(arr); (gdb) b 12 Breakpoint 1 at 0x1201: file t2.c, line 12. (gdb) r Starting program: /home/minimonk/work/src/malloc/a.out [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Breakpoint 1, main () at t2.c:12 12 printf("before\n"); (gdb) print idx $1 = 10000 (gdb) n malloc(): corrupted top size Program received signal SIGABRT, Aborted. __pthread_kill_implementation (no_tid=0, signo=6, threadid=140737353705280) at ./nptl/pthread_kill.c:44 44 ./nptl/pthread_kill.c: 그런 파일이나 디렉터리가 없습니다. |
그래서 printf / fflush 주석처리하고 free를 바로 브레이크 포인트 잡아서 해도 동일하게
루프 종료되면서 바로 에러가 나는 것 같기도...
| 15 free(arr); (gdb) c Continuing. malloc(): corrupted top size Program received signal SIGABRT, Aborted. __pthread_kill_implementation (no_tid=0, signo=6, threadid=140737353705280) at ./nptl/pthread_kill.c:44 44 ./nptl/pthread_kill.c: 그런 파일이나 디렉터리가 없습니다. (gdb) |
| float 자릿수 제한 (0) | 2025.10.11 |
|---|---|
| free(): invalid next size (normal) (0) | 2023.12.18 |
| c에서 cpp 함수 불러오기 (0) | 2023.01.04 |
| MSB / LSB 변환 (0) | 2022.08.29 |
| kore - c restful api server (1) | 2022.07.07 |
socket은 좀더 찾아 봐야 할 것 같은데
내부통신이 아닌 외부장치와의 통신은 qt quick / qml 에서 어떻게 구현되는지 궁금해짐

| qmlwebsocketserver main.qml | qmlwebsocketclient main.qml |
| // Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick import QtWebSockets Rectangle { width: 360 height: 360 function appendMessage(message) { messageBox.text += "\n" + message } WebSocketServer { id: server listen: true onClientConnected: function(webSocket) { webSocket.onTextMessageReceived.connect(function(message) { appendMessage(qsTr("Server received message: %1").arg(message)); webSocket.sendTextMessage(qsTr("Hello Client!")); }); } onErrorStringChanged: { appendMessage(qsTr("Server error: %1").arg(errorString)); } } WebSocket { id: socket url: server.url onTextMessageReceived: function(message) { appendMessage(qsTr("Client received message: %1").arg(message)); } onStatusChanged: { if (socket.status == WebSocket.Error) { appendMessage(qsTr("Client error: %1").arg(socket.errorString)); } else if (socket.status == WebSocket.Closed) { appendMessage(qsTr("Client socket closed.")); } } } Timer { interval: 100 running: true onTriggered: { socket.active = true; } } Text { id: messageBox text: qsTr("Click to send a message!") anchors.fill: parent MouseArea { anchors.fill: parent onClicked: { socket.sendTextMessage(qsTr("Hello Server!")); } } } } |
// Copyright (C) 2016 Kurt Pattyn <pattyn.kurt@gmail.com>. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick import QtWebSockets Rectangle { width: 640 height: 360 WebSocket { id: socket url: "ws://ws.ifelse.io" onTextMessageReceived: function(message) { messageBox.text = messageBox.text + "\nReceived message: " + message } onStatusChanged: if (socket.status == WebSocket.Error) { console.log("Error: " + socket.errorString) } else if (socket.status == WebSocket.Open) { socket.sendTextMessage("Hello World") } else if (socket.status == WebSocket.Closed) { messageBox.text += "\nSocket closed" } active: false } WebSocket { id: secureWebSocket url: "wss://ws.ifelse.io" onTextMessageReceived: function(message) { messageBox.text = messageBox.text + "\nReceived secure message: " + message } onStatusChanged: if (secureWebSocket.status == WebSocket.Error) { console.log("Error: " + secureWebSocket.errorString) } else if (secureWebSocket.status == WebSocket.Open) { secureWebSocket.sendTextMessage("Hello Secure World") } else if (secureWebSocket.status == WebSocket.Closed) { messageBox.text += "\nSecure socket closed" } active: false } Text { id: messageBox text: socket.status == WebSocket.Open ? qsTr("Sending...") : qsTr("Welcome!") anchors.centerIn: parent } MouseArea { anchors.fill: parent onClicked: { socket.active = !socket.active secureWebSocket.active = !secureWebSocket.active; //Qt.quit(); } } } |
다른 예제
| qt qml 와 c++ 상호연동 (0) | 2026.01.16 |
|---|---|
| qt qml loader (0) | 2026.01.14 |
| qstackedwidget, qstackedlayout (0) | 2026.01.13 |
| qt quick 예제 calqlatr 코드분석 (0) | 2026.01.13 |
| qt qml view (0) | 2026.01.13 |
로더라는 말은 몇번 봤는데 예제는 이제 찾아봄.
구현 자체는 무지 쉬워보이는데 라이프 사이클이라던가 좀 봐야할 듯.
| import QtQuick Item { width: 200; height: 200 Loader { id: pageLoader } MouseArea { anchors.fill: parent onClicked: pageLoader.source = "Page1.qml" } } |
[링크 : https://doc.qt.io/qt-6/ko/qml-qtquick-loader.html]
| qt qml 와 c++ 상호연동 (0) | 2026.01.16 |
|---|---|
| qt quick websocket (0) | 2026.01.14 |
| qstackedwidget, qstackedlayout (0) | 2026.01.13 |
| qt quick 예제 calqlatr 코드분석 (0) | 2026.01.13 |
| qt qml view (0) | 2026.01.13 |
dialog 띄우고 show, hide 하는것도 방법이지만
layout 에다가 widget을 추가하고 layout을 stack 해서 새 창을 띄울 것들을 새로운 stack에 넣어서 하는 것도 방법이고
아니면 위젯을 스택해서 쓰는것도 방법일 듯 한데
구조적으로는 stackedlayout에 widget을 넣어 운영하는게 좀 더 맞는 듯?
| QStackedLayout | QStackedWidget |
| import sys, os # Qt 바인딩을 PySide6 우선 사용하고, 실패 시 PyQt6 사용 qt_modules = None # PySide6을 먼저 시도 try: from PySide6.QtWidgets import ( QApplication, QWidget, QStackedLayout, QVBoxLayout, QLabel, QComboBox, ) from PySide6.QtGui import QPixmap qt_modules = 'PySide6' except ImportError: # 실패 시 PyQt6 시도 try: from PyQt6.QtWidgets import ( QApplication, QWidget, QStackedLayout, QVBoxLayout, QLabel, QComboBox, ) from PyQt6.QtGui import QPixmap qt_modules = 'PyQt6' except ImportError: # 둘 다 실패하면 메시지 출력 후 종료 print("There is no Qt Binding for Python.") sys.exit(1) # 사용된 Qt 바인딩 이름 출력 print(f"Using {qt_modules} binding.") # ------------------------------------ # 메인 윈도우 클래스 정의 class MW(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): # 윈도우 타이틀 설정 self.setWindowTitle("Ex Input Widgets") # 메인 위젯 및 레이아웃 구성 self.setup_main_wnd() # 윈도우 표시 self.show() def setup_main_wnd(self): # 현재 파일의 절대 경로 기준 디렉토리 경로 얻기 fpath = os.path.dirname( os.path.abspath(__file__) ) # 콤보박스에 표시될 페이지 이름들 pages = ['faith', 'hope', 'love'] # 각 페이지에 해당하는 이미지 파일 경로 리스트 self.imgs = [ os.path.join(fpath, 'img','faith.png'), os.path.join(fpath, 'img','hope.png'), os.path.join(fpath, 'img','love.png') ] # 콤보박스 생성 및 항목 추가 combo_box = QComboBox() combo_box.addItems(pages) # 콤보박스 항목이 선택되면 change_page 함수 실행 combo_box.activated.connect(self.change_page) # QStackedLayout 생성 (여러 페이지를 겹쳐서 보관, 하나만 보여줌) self.stacked_lm = QStackedLayout() # 페이지 수만큼 QLabel 생성 후 QStackedLayout에 추가 for idx, c in enumerate(pages): label = self.setup_page(idx) # QLabel을 생성하고 이미지 설정 self.stacked_lm.addWidget(label) # 스택에 추가 # 수직 박스 레이아웃 생성 v_box_lm = QVBoxLayout() v_box_lm.addWidget(combo_box) # 콤보박스를 위에 추가 # 스택 레이아웃을 아래에 추가 # v_box_lm.addLayout(self.stacked_lm) # 이 한줄로 아래 3개라인을 대체 가능. tmp_c = QWidget() tmp_c.setLayout(self.stacked_lm) v_box_lm.addWidget(tmp_c) # 최종 레이아웃을 윈도우에 설정 self.setLayout(v_box_lm) # 페이지용 QLabel 설정 함수 def setup_page(self, page_num): label = QLabel() pixmap = QPixmap(self.imgs[page_num]) # 해당 이미지 불러오기 label.setPixmap(pixmap) # QLabel에 이미지 설정 label.setScaledContents(True) # QLabel 크기에 맞게 이미지 자동 조절 return label # 콤보박스에서 선택된 인덱스에 해당하는 페이지를 보여줌 def change_page(self, idx): self.stacked_lm.setCurrentIndex(idx) # ------------------------------------ # 프로그램 진입점 if __name__ == "__main__": print(os.path.realpath(__file__)) # 현재 실행 중인 파일 경로 출력 app = QApplication(sys.argv) # QApplication 인스턴스 생성 main_wnd = MW() # 메인 윈도우 생성 sys.exit(app.exec()) # 이벤트 루프 실행 |
import sys, os # Qt 바인딩을 PySide6 우선, PyQt6는 백업용 qt_modules = None try: from PySide6.QtWidgets import ( QApplication, QWidget, QLabel, QVBoxLayout, QComboBox, QStackedWidget ) from PySide6.QtGui import QPixmap qt_modules = 'PySide6' except ImportError: try: from PyQt6.QtWidgets import ( QApplication, QWidget, QLabel, QVBoxLayout, QComboBox, QStackedWidget ) from PyQt6.QtGui import QPixmap qt_modules = 'PyQt6' except ImportError: print("There is no Qt Binding for Python.") sys.exit(1) print(f"Using {qt_modules} binding.") # ------------------------------------ class MW(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): self.setWindowTitle("Ex: QStackedWidget with ComboBox") self.setup_main_wnd() self.show() def setup_main_wnd(self): # 현재 스크립트 경로를 기준으로 이미지 파일 위치 설정 fpath = os.path.dirname(os.path.abspath(__file__)) # 페이지 이름과 이미지 경로 정의 pages = ['faith', 'hope', 'love'] self.imgs = [ os.path.join(fpath, 'img/faith.png'), os.path.join(fpath, 'img/hope.png'), os.path.join(fpath, 'img/love.png') ] # 콤보박스 생성 및 페이지 이름 추가 combo_box = QComboBox() combo_box.addItems(pages) combo_box.activated.connect(self.change_page) # QStackedWidget 생성 self.stack_widget = QStackedWidget() # 각 페이지에 해당하는 QLabel + 이미지 추가 for idx in range(len(pages)): label = QLabel() pixmap = QPixmap(self.imgs[idx]) label.setPixmap(pixmap) label.setScaledContents(True) # 이미지가 QLabel에 맞게 리사이즈됨 self.stack_widget.addWidget(label) # 수직 레이아웃 구성: 콤보박스 위, 이미지 아래 layout = QVBoxLayout() layout.addWidget(combo_box) layout.addWidget(self.stack_widget) self.setLayout(layout) # 콤보박스 인덱스 선택 시 보여줄 페이지 변경 def change_page(self, idx): self.stack_widget.setCurrentIndex(idx) # ------------------------------------ if __name__ == "__main__": print(os.path.realpath(__file__)) app = QApplication(sys.argv) main_wnd = MW() sys.exit(app.exec()) |
[링크 : https://wikidocs.net/185879]
qstackedwidget
[링크 : https://wikidocs.net/162838]
[링크 : https://doc.qt.io/qt-6/qwidget.html]
[링크 : https://doc.qt.io/qt-6/qstackedwidget.html]
[링크 : https://qt-dev.com/board.php?board=qnaboard&page=4&category=1&command=body&no=758]
[링크 : https://hobbylife.tistory.com/entry/PySide6-QStackedWidget-완전-정복-–-클릭-이벤트와-사용법]
qstackedlayout
| qt quick websocket (0) | 2026.01.14 |
|---|---|
| qt qml loader (0) | 2026.01.14 |
| qt quick 예제 calqlatr 코드분석 (0) | 2026.01.13 |
| qt qml view (0) | 2026.01.13 |
| qt quick 이미지 클릭 (0) | 2026.01.12 |
qt quick 으로 작성된 계산기 어플 소스 분석

실행하면 평범한 계산기가 뜬다.

가로로 길게 늘리면 공학계산기로 바뀐다.

프로젝트는 아래와 같이 구성되어 있고

언제나 그렇듯(?) main.cpp 는 조촐하고 별 내용이 없다.
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>
int main(int argc, char *argv[])
{
QCoreApplication::setOrganizationName("QtProject");
QCoreApplication::setApplicationName("Calqlatr");
QGuiApplication app(argc, argv);
QQuickStyle::setStyle("Basic");
QQmlApplicationEngine engine;
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
&app, []() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("demos.calqlatr", "Main");
return app.exec();
}
전체 모양을 그리는 녀석인데, design 에서 보면 아래처럼 보인다.

다만 qt designer의 버그인지 ApplicationState 의 id: state 구문이 에러가 나는데
위지윅 에디터에서 보려면 해당 라인을 주석처리 하면 된다. (물론 실행하면 작동안하게 되는 버그 발생)

column, row Layout 으로 배치를 어떻게 하는것 같고
Keys.onPressed: function (event) {} 를 통해서 키 입력시 state에 추가하는 식으로 작동하게 된다.
| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick import QtQuick.Layouts Window { visible: true width: 320 height: 480 minimumWidth: Math.max(numberPad.portraitModeWidth, display.minWidth) + root.margin * 2 minimumHeight: display.minHeight + numberPad.height + root.margin * 3 color: root.backgroundColor Item { id: root anchors.fill: parent anchors.topMargin: parent.SafeArea.margins.top anchors.leftMargin: parent.SafeArea.margins.left anchors.rightMargin: parent.SafeArea.margins.right anchors.bottomMargin: parent.SafeArea.margins.bottom readonly property int margin: 18 readonly property color backgroundColor: "#222222" readonly property int minLandscapeModeWidth: numberPad.landscapeModeWidth + display.minWidth + margin * 3 property bool isPortraitMode: root.width < root.minLandscapeModeWidth ApplicationState { id: state display: display } Display { id: display readonly property int minWidth: 210 readonly property int minHeight: 60 Layout.minimumWidth: minWidth Layout.fillWidth: true Layout.fillHeight: true Layout.margins: root.margin // remove the margin on the side that the numberPad is on, to prevent a double margin Layout.bottomMargin: root.isPortraitMode ? 0 : root.margin Layout.rightMargin: root.isPortraitMode ? root.margin : 0 } NumberPad { id: numberPad Layout.margins: root.margin isPortraitMode: root.isPortraitMode state: state } // define the responsive layouts ColumnLayout { id: portraitMode anchors.fill: parent visible: root.isPortraitMode LayoutItemProxy { target: display Layout.minimumHeight: display.minHeight } LayoutItemProxy { target: numberPad Layout.alignment: Qt.AlignHCenter } } RowLayout { id: landscapeMode anchors.fill: parent visible: !root.isPortraitMode LayoutItemProxy { target: display } LayoutItemProxy { target: numberPad Layout.alignment: Qt.AlignVCenter } } Keys.onPressed: function (event) { switch (event.key) { case Qt.Key_0: state.digitPressed("0"); break; case Qt.Key_1: state.digitPressed("1"); break; case Qt.Key_2: state.digitPressed("2"); break; case Qt.Key_3: state.digitPressed("3"); break; case Qt.Key_4: state.digitPressed("4"); break; case Qt.Key_5: state.digitPressed("5"); break; case Qt.Key_6: state.digitPressed("6"); break; case Qt.Key_7: state.digitPressed("7"); break; case Qt.Key_8: state.digitPressed("8"); break; case Qt.Key_9: state.digitPressed("9"); break; case Qt.Key_E: state.digitPressed("e"); break; case Qt.Key_P: state.digitPressed("π"); break; case Qt.Key_Plus: state.operatorPressed("+"); break; case Qt.Key_Minus: state.operatorPressed("-"); break; case Qt.Key_Asterisk: state.operatorPressed("×"); break; case Qt.Key_Slash: state.operatorPressed("÷"); break; case Qt.Key_Enter: case Qt.Key_Return: state.operatorPressed("="); break; case Qt.Key_Comma: case Qt.Key_Period: state.digitPressed("."); break; case Qt.Key_Backspace: state.operatorPressed("bs"); break; } } } } |
말로는 qml 인데 design에서 보여지는 요소는 존재하지 않고
main.qml 에서 호출되는 operatorPressed() 나 digitPressed()와 같은 함수가 존재한다.
특이한건 import as 인데 js를 불러서 CalcEngine 으로 사용하는 부분 정도?
그 와중에 Display는 display.qml 에서 끌려오는건가?
| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQml import "calculator.js" as CalcEngine QtObject { required property Display display function operatorPressed(operator) { CalcEngine.operatorPressed(operator, display); } function digitPressed(digit) { CalcEngine.digitPressed(digit, display); } function isButtonDisabled(op) { return CalcEngine.isOperationDisabled(op, display); } } |
평범한(?) js로 작성된 코드가 존재한다.
| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause let accumulator = 0 let pendingOperator = "" let lastButton = "" let digits = "" function isOperationDisabled(op, display) { if (digits !== "" && lastButton !== "=" && (op === "π" || op === "e")) return true if (digits === "" && !((op >= "0" && op <= "9") || op === "π" || op === "e" || op === "AC")) return true if (op === "bs" && (display.isOperandEmpty() || !((lastButton >= "0" && lastButton <= "9") || lastButton === "π" || lastButton === "e" || lastButton === "."))) return true if (op === '=' && pendingOperator.length != 1) return true if (op === "." && digits.search(/\./) != -1) return true if (op === "√" && digits.search(/-/) != -1) return true if (op === "AC" && display.isDisplayEmpty()) return true return false } function digitPressed(op, display) { if (isOperationDisabled(op, display)) return if (lastButton === "π" || lastButton === "e") return // handle mathematical constants if (op === "π") { lastButton = op digits = Math.PI.toPrecision(display.maxDigits - 1).toString() display.appendDigit(digits) return } if (op === "e") { lastButton = op digits = Math.E.toPrecision(display.maxDigits - 1).toString() display.appendDigit(digits) return } // append a digit to another digit or decimal point if (lastButton.toString().length === 1 && ((lastButton >= "0" && lastButton <= "9") || lastButton === ".") ) { if (digits.length >= display.maxDigits) return digits = digits + op.toString() display.appendDigit(op.toString()) // else just write a single digit to display } else { digits = op.toString() display.appendDigit(digits) } lastButton = op } function operatorPressed(op, display) { if (isOperationDisabled(op, display)) return if (op === "±") { digits = Number(digits.valueOf() * -1).toString() display.setDigit(display.displayNumber(Number(digits))) return } if (op === "bs") { digits = digits.slice(0, -1) if (digits === "-") digits = "" display.backspace() return } lastButton = op if (pendingOperator === "+") { digits = (Number(accumulator) + Number(digits.valueOf())).toString() } else if (pendingOperator === "−") { digits = (Number(accumulator) - Number(digits.valueOf())).toString() } else if (pendingOperator === "×") { digits = (Number(accumulator) * Number(digits.valueOf())).toString() } else if (pendingOperator === "÷") { digits = (Number(accumulator) / Number(digits.valueOf())).toString() } if (op === "+" || op === "−" || op === "×" || op === "÷") { pendingOperator = op accumulator = digits.valueOf() digits = "" display.displayOperator(pendingOperator) return } accumulator = 0 pendingOperator = "" if (op === "=") { display.newLine("=", Number(digits)) } if (op === "√") { digits = (Math.sqrt(digits.valueOf())).toString() display.newLine("√", Number(digits)) } else if (op === "⅟x") { digits = (1 / digits.valueOf()).toString() display.newLine("⅟x", Number(digits)) } else if (op === "x²") { digits = (digits.valueOf() * digits.valueOf()).toString() display.newLine("x²", Number(digits)) } else if (op === "x³") { digits = (digits.valueOf() * digits.valueOf() * digits.valueOf()).toString() display.newLine("x³", Number(digits)) } else if (op === "|x|") { digits = (Math.abs(digits.valueOf())).toString() display.newLine("|x|", Number(digits)) } else if (op === "⌊x⌋") { digits = (Math.floor(digits.valueOf())).toString() display.newLine("⌊x⌋", Number(digits)) } else if (op === "sin") { digits = Number(Math.sin(digits.valueOf())).toString() display.newLine("sin", Number(digits)) } else if (op === "cos") { digits = Number(Math.cos(digits.valueOf())).toString() display.newLine("cos", Number(digits)) } else if (op === "tan") { digits = Number(Math.tan(digits.valueOf())).toString() display.newLine("tan", Number(digits)) } else if (op === "log") { digits = Number(Math.log10(digits.valueOf())).toString() display.newLine("log", Number(digits)) } else if (op === "ln") { digits = Number(Math.log(digits.valueOf())).toString() display.newLine("ln", Number(digits)) } if (op === "AC") { display.allClear() accumulator = 0 lastButton = "" digits = "" pendingOperator = "" } } |
design 상에서는 별 내용이 없어 보이지만

코드에서는 라인별로 추가하는 등 제법 ui를 건드리는 작동을 많이 한다.
| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause pragma ComponentBehavior: Bound import QtQuick Item { id: display property int fontSize: 22 readonly property int maxDigits: Math.min((width / fontSize) + 1, 9) readonly property color backgroundColor: "#262626" readonly property color qtGreenColor: "#2CDE85" property string displayedOperand: "" readonly property string errorString: qsTr("ERROR") readonly property bool isError: displayedOperand === errorString property bool enteringDigits: false function displayOperator(operator) { calculationsListView.model.append({ "operator": operator, "operand": "" }); enteringDigits = true; calculationsListView.positionViewAtEnd(); } function newLine(operator, operand) { displayedOperand = displayNumber(operand); calculationsListView.model.append({ "operator": operator, "operand": displayedOperand }); enteringDigits = false; calculationsListView.positionViewAtEnd(); } function appendDigit(digit) { if (!enteringDigits) calculationsListView.model.append({ "operator": "", "operand": "" }); const i = calculationsListView.model.count - 1; calculationsListView.model.get(i).operand = calculationsListView.model.get(i).operand + digit; enteringDigits = true; calculationsListView.positionViewAtEnd(); } function setDigit(digit) { const i = calculationsListView.model.count - 1; calculationsListView.model.get(i).operand = digit; calculationsListView.positionViewAtEnd(); } function backspace() { const i = calculationsListView.model.count - 1; if (i >= 0) { let operand = calculationsListView.model.get(i).operand.toString().slice(0, -1); if (operand === "-") operand = ""; calculationsListView.model.get(i).operand = operand; return; } return; } function isOperandEmpty() { const i = calculationsListView.model.count - 1; return i >= 0 ? calculationsListView.model.get(i).operand === "" : true; } function isDisplayEmpty() { const i = calculationsListView.model.count - 1; return i == -1 ? true : (i == 0 ? calculationsListView.model.get(0).operand === "" : false); } function clear() { displayedOperand = ""; if (enteringDigits) { const i = calculationsListView.model.count - 1; if (i >= 0) calculationsListView.model.remove(i); enteringDigits = false; } } function allClear() { display.clear(); calculationsListView.model.clear(); enteringDigits = false; } // Returns a string representation of a number that fits in // display.maxDigits characters, trying to keep as much precision // as possible. If the number cannot be displayed, returns an // error string. function displayNumber(num) { if (typeof (num) !== "number") return errorString; // deal with the absolute const abs = Math.abs(num); if (abs.toString().length <= maxDigits) { return isFinite(num) ? num.toString() : errorString; } if (abs < 1) { // check if abs < 0.00001, if true, use exponential form // if it isn't true, we can round the number without losing // too much precision if (Math.floor(abs * 100000) === 0) { const expVal = num.toExponential(maxDigits - 6).toString(); if (expVal.length <= maxDigits + 1) return expVal; } else { // the first two digits are zero and . return num.toFixed(maxDigits - 2); } } else { // if the integer part of num is greater than maxDigits characters, use exp form const intAbs = Math.floor(abs); if (intAbs.toString().length <= maxDigits) return parseFloat(num.toPrecision(maxDigits - 1)).toString(); const expVal = num.toExponential(maxDigits - 6).toString(); if (expVal.length <= maxDigits + 1) return expVal; } return errorString; } Item { anchors.fill: parent Rectangle { anchors.fill: parent radius: 8 color: display.backgroundColor ListView { id: calculationsListView x: 5 y: 10 width: parent.width height: parent.height - 2 * y clip: true delegate: Item { height: display.fontSize * 1.1 width: calculationsListView.width required property string operator required property string operand Text { x: 6 font.pixelSize: display.fontSize color: display.qtGreenColor text: parent.operator Accessible.name: parent.operator } Text { font.pixelSize: display.fontSize anchors.right: parent.right anchors.rightMargin: 16 text: parent.operand Accessible.name: parent.operand color: "white" } } model: ListModel {} onHeightChanged: positionViewAtEnd() } } } } |
계산기의 버튼 부분인것 같은데 rectangle 안에 Rowlayout 안에 GridLayout이 있는데 그래서 그런가 이상하게 보여지는 느낌.

그나저나 제곱이나 sin tan 같은 과학계산기도 넣으려다가 흔적만 남은건가?
| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts Item { id: controller required property bool isPortraitMode required property ApplicationState state readonly property color qtGreenColor: "#2CDE85" readonly property color backspaceRedColor: "#DE2C2C" readonly property int spacing: 5 property int portraitModeWidth: mainGrid.width property int landscapeModeWidth: scientificGrid.width + mainGrid.width implicitWidth: isPortraitMode ? portraitModeWidth : landscapeModeWidth implicitHeight: mainGrid.height function updateDimmed() { for (let i = 0; i < mainGrid.children.length; i++) { mainGrid.children[i].dimmed = state.isButtonDisabled(mainGrid.children[i].text); } for (let j = 0; j < scientificGrid.children.length; j++) { scientificGrid.children[j].dimmed = state.isButtonDisabled( scientificGrid.children[j].text); } } component DigitButton: CalculatorButton { onClicked: { controller.state.digitPressed(text); controller.updateDimmed(); } } component OperatorButton: CalculatorButton { dimmable: true implicitWidth: 48 textColor: controller.qtGreenColor onClicked: { controller.state.operatorPressed(text); controller.updateDimmed(); } } Component.onCompleted: updateDimmed() Rectangle { id: numberPad anchors.fill: parent radius: 8 color: "transparent" RowLayout { spacing: controller.spacing GridLayout { id: scientificGrid columns: 3 columnSpacing: controller.spacing rowSpacing: controller.spacing visible: !controller.isPortraitMode OperatorButton { text: "x²" Accessible.name: "x squared" } OperatorButton { text: "⅟x" Accessible.name: "one over x" } OperatorButton { text: "√" } OperatorButton { text: "x³" Accessible.name: "x cubed" } OperatorButton { text: "sin" Accessible.name: "sine" } OperatorButton { text: "|x|" Accessible.name: "absolute value" } OperatorButton { text: "log" } OperatorButton { text: "cos" Accessible.name: "cosine" } DigitButton { text: "e" dimmable: true implicitWidth: 48 } OperatorButton { text: "ln" } OperatorButton { text: "tan" } DigitButton { text: "π" dimmable: true implicitWidth: 48 } } GridLayout { id: mainGrid columns: 5 columnSpacing: controller.spacing rowSpacing: controller.spacing BackspaceButton { onClicked: { controller.state.operatorPressed(this.text); controller.updateDimmed(); } } DigitButton { text: "7" } DigitButton { text: "8" } DigitButton { text: "9" } OperatorButton { text: "÷" implicitWidth: 38 } OperatorButton { text: "AC" textColor: controller.backspaceRedColor accentColor: controller.backspaceRedColor } DigitButton { text: "4" } DigitButton { text: "5" } DigitButton { text: "6" } OperatorButton { text: "×" implicitWidth: 38 } OperatorButton { text: "=" implicitHeight: 81 Layout.rowSpan: 2 } DigitButton { text: "1" } DigitButton { text: "2" } DigitButton { text: "3" } OperatorButton { text: "−" implicitWidth: 38 } OperatorButton { text: "±" implicitWidth: 38 } DigitButton { text: "0" } DigitButton { text: "." dimmable: true } OperatorButton { text: "+" implicitWidth: 38 } } } // RowLayout } } |
numpad 에서 호출되는 버튼. 그 외에는 크게 눈에 띄진 않네

| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick import QtQuick.Controls RoundButton { id: button implicitWidth: 48 implicitHeight: 38 radius: buttonRadius icon.source: getIcon() icon.width: 38 icon.height: 38 icon.color: getIconColor() // include this text property as the calculator engine // differentiates buttons through text. The text is never drawn. text: "bs" Accessible.name: "backspace" property bool dimmable: true property bool dimmed: false readonly property color backgroundColor: "#222222" readonly property color borderColor: "#A9A9A9" readonly property color backspaceRedColor: "#DE2C2C" readonly property int buttonRadius: 8 function getBackgroundColor() { if (button.dimmable && button.dimmed) return backgroundColor; if (button.pressed) return backspaceRedColor; return backgroundColor; } function getBorderColor() { if (button.dimmable && button.dimmed) return borderColor; if (button.pressed || button.hovered) return backspaceRedColor; return borderColor; } function getIconColor() { if (button.dimmable && button.dimmed) return Qt.darker(backspaceRedColor); if (button.pressed) return backgroundColor; return backspaceRedColor; } function getIcon() { if (button.dimmable && button.dimmed) return "images/backspace.svg"; if (button.pressed) return "images/backspace_fill.svg"; return "images/backspace.svg"; } background: Rectangle { radius: button.buttonRadius color: button.getBackgroundColor() border.color: button.getBorderColor() } } |
numpad 에서 호출되는 버튼. 그 외에는 크게 눈에 띄진 않네 2

| // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick import QtQuick.Controls RoundButton { id: button implicitWidth: 38 implicitHeight: 38 radius: buttonRadius property bool dimmable: false property bool dimmed: false readonly property int fontSize: 22 readonly property int buttonRadius: 8 property color textColor: "#FFFFFF" property color accentColor: "#2CDE85" readonly property color backgroundColor: "#222222" readonly property color borderColor: "#A9A9A9" function getBackgroundColor() { if (button.dimmable && button.dimmed) return backgroundColor; if (button.pressed) return accentColor; return backgroundColor; } function getBorderColor() { if (button.dimmable && button.dimmed) return borderColor; if (button.pressed || button.hovered) return accentColor; return borderColor; } function getTextColor() { if (button.dimmable && button.dimmed) return Qt.darker(textColor); if (button.pressed) return backgroundColor; if (button.hovered) return accentColor; return textColor; } background: Rectangle { radius: button.buttonRadius color: button.getBackgroundColor() border.color: button.getBorderColor() } contentItem: Text { text: button.text font.pixelSize: button.fontSize horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: button.getTextColor() Behavior on color { ColorAnimation { duration: 120 easing.type: Easing.OutElastic } } } } |
| qt qml loader (0) | 2026.01.14 |
|---|---|
| qstackedwidget, qstackedlayout (0) | 2026.01.13 |
| qt qml view (0) | 2026.01.13 |
| qt quick 이미지 클릭 (0) | 2026.01.12 |
| qt creator 18.0.1 design 활성화 하기 (0) | 2026.01.09 |