이 페이지에서

Calqlatr

가로 및 세로 기기용으로 설계된 계산기입니다. 사용자 인터페이스에는 Qt Quick 사용자 정의 구성 요소와 반응형 레이아웃을, 애플리케이션 로직에는 JavaScript를 사용합니다.

계산기 예제 UI

Calqlatr는 다양한 QML 및 Qt Quick 사용자 정의 컴포넌트 표시 및 반응형 레이아웃 사용과 같은 다양한 기능을 보여줍니다. 애플리케이션 로직은 JavaScript로 구현되고 UI는 QML로 구현됩니다.

예제 실행하기

에서 예제를 실행하려면 Qt Creator에서 Welcome 모드를 열고 Examples 에서 예제를 선택합니다. 자세한 내용은 Qt Creator: 튜토리얼을 참조하세요 : 빌드 및 실행을 참조하세요. 예제를 실행한 후에는 애플리케이션을 표준 계산기로 사용할 수 있어야 합니다. 휴대폰을 세로 모드에서 가로 모드로 변경하거나 데스크톱에서 기본 창 크기를 조정하여 반응형 레이아웃을 활성화합니다.

사용자 정의 구성 요소 사용

캘클라트르 애플리케이션은 사용자 정의 유형을 사용합니다. 대부분은 별도의 .qml 파일에 정의되어 있습니다:

  • Main.qml
  • content/ApplicationState.qml
  • content/BackspaceButton.qml
  • content/CalculatorButton.qml
  • content/Display.qml
  • content/NumberPad.qml

Main.qml 파일에는 최상위 창과 루트 항목이 포함되어 있습니다. content 디렉토리에 있는 다른 사용자 정의 유형을 사용합니다. 예를 들어 NumberPad 유형( content/NumberPad.qml 에 정의됨)은 Main.qml 에서 계산기의 숫자 패드를 만드는 데 사용됩니다:

        NumberPad {
            id: numberPad
            Layout.margins: root.margin

            isPortraitMode: root.isPortraitMode
            applicationState: state
        }

인라인 컴포넌트를 사용하면 .qml 파일 안에 여러 컴포넌트를 선언할 수 있습니다. 이 예에서는 NumberPad.qml 에서 인라인 컴포넌트를 사용하여 DigitButtonOperatorButton 라는 두 개의 컴포넌트를 정의합니다:

    component DigitButton: CalculatorButton {
        onClicked: {
            controller.applicationState.digitPressed(text);
            controller.updateDimmed();
        }
    }

    component OperatorButton: CalculatorButton {
        dimmable: true
        implicitWidth: 48
        textColor: controller.qtGreenColor

        onClicked: {
            controller.applicationState.operatorPressed(text);
            controller.updateDimmed();
        }
    }

두 컴포넌트 모두 CalculatorButton 유형( CalculatorButton.qml 에 정의된 대로)이지만 클릭된 신호에 대한 사용자 정의 핸들러를 제공하고 일부 속성 기본값을 조정합니다. DigitButtonOperatorButton 는 나중에 NumberPad.qml 에서 인스턴스화됩니다:

                DigitButton {
                    text: "e"
                    dimmable: true
                    implicitWidth: 48
                }
                OperatorButton { text: "ln" }

사용자 정의 QML 컴포넌트 정의에 대한 자세한 내용은 QML 문서를 통해 객체 유형 정의하기를 참조하세요.

반응형 레이아웃

이 예제에서 반응형 레이아웃은 세로 모드와 가로 모드 모두에 대해 서로 다른 UI 컴포넌트를 정렬합니다. Main.qml 에서 이를 확인할 수 있으며, 여기에는 세로 모드용 ColumnLayout 과 가로 모드용 RowLayout 이 정의되어 있습니다:

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

ColumnLayout 은 애플리케이션의 세로 레이아웃을 나타내고 RowLayout 은 가로 레이아웃을 나타냅니다. visible 속성은 주어진 시간에 어떤 레이아웃이 사용되는지를 처리합니다. NumberPadDisplay 컴포넌트의 id 속성은 LayoutItemProxy 유형의 target 속성을 설정하는 데 사용됩니다. 이렇게 하면 두 레이아웃이 동일한 콘텐츠 항목을 사용할 수 있습니다. 또한 LayoutItemProxy 항목 내에서 속성을 target 자체로 전달할 수 있습니다. 예를 들어 NumberPad 가 인스턴스화되면 두 레이아웃 모두 다른 Layout.alignment 이 필요합니다.

반응형 레이아웃은 NumberPad 자체의 세로 및 가로 레이아웃을 정의할 때 NumberPad.qml 에서도 사용됩니다:

        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.applicationState.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

controller.isPortraitMode 에 따라 숫자 패드에는 간단한 계산기 버튼(mainGrid)만 포함되거나 scientificGrid 을 통해 고급 기능이 포함될 수도 있습니다.

버튼 텍스트 색상 애니메이션

CalculatorButton.qml 파일에서는 숫자 패드 버튼의 텍스트 색상에도 애니메이션을 적용합니다.

        ...
        color: button.getTextColor()
        Behavior on color {
            ColorAnimation {
                duration: 120
                easing.type: Easing.OutElastic
            }
        }
    }

색상 변경은 color 속성에서 Behavior 을 정의하여 애니메이션을 적용합니다. 버튼이 dimmed = true 로 설정되어 있으면 버튼이 더 어둡게 나타납니다. 버튼을 누르면 버튼이 녹색으로 켜집니다. NumberPad 에 있는 모든 버튼의 dimmed 속성을 동적으로 변경하기 위해 buttonPressed 신호는 NumberPadupdateDimmed() 함수를 호출합니다.

    function updateDimmed() {
        for (let i = 0; i < mainGrid.children.length; i++) {
            mainGrid.children[i].dimmed = applicationState.isButtonDisabled(mainGrid.children[i].text);
        }
        for (let j = 0; j < scientificGrid.children.length; j++) {
            scientificGrid.children[j].dimmed = applicationState.isButtonDisabled(
                        scientificGrid.children[j].text);
        }
    }

계산 수행

calculator.jsApplicationState.qml 파일은 계산기의 엔진을 정의합니다. calculator.js 에는 계산기의 논리적 상태와 상태를 변경하는 연산이 포함되어 있습니다. ApplicationState.qml 은 이 API를 QML 타입으로 노출합니다.

먼저 calculator.js 을 살펴봅시다:

let accumulator = 0
let pendingOperator = ""
let lastButton = ""
let digits = ""

accumulator, pendingOperator, lastButton, represent 계산기의 논리적 상태를 반환합니다.

function isOperationDisabled(op, display) {
    ...
}

isOperationDisabled() 현재 엔진 및 표시 상태에 따라 op 연산이 비활성화되어야 하는 경우 true 를 반환합니다. 그렇지 않으면 false 을 반환합니다.

function digitPressed(op, display) {
    ...
}

function operatorPressed(op, display) {
    ...
}

digitPressed()operatorPressed() 함수는 눌린 버튼에 따라 엔진과 디스플레이 상태를 업데이트합니다.

ApplicationState.qmlcalculator.js 에 정의된 자바스크립트 함수를 더 멋진 QML API 뒤에 노출합니다:

import QtQml
import "calculator.js" as CalcEngine

calculator.jsCalcEngine 라는 이름으로 가져옵니다.

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);
    }
}

required property ApplicationState 에서 display 라는 이름으로 Display.qml 의 인스턴스를 항상 사용할 수 있도록 합니다. displayoperatorPressed(), digitPressed()isButtonDisabled() 함수에서 차례로 사용됩니다.

소스 파일

Squish GUI 테스트

이 애플리케이션은 안드로이드용 Qt를 대상으로 하는 Squish GUI 테스트와 함께 제공됩니다. 테스트는 Squish for Qt로 만들어졌으며 Python으로 작성되었으며 애플리케이션 테스트 디렉토리에서 찾을 수 있습니다.

Android용 Qt용 Squish를 사용할 때는 Qt에 내장된 후크를 사용하고 adb로 Squish 포트를 전달해야 합니다. dlopen 실패와 함께 "라이브러리를 로드할 수 없습니다"라는 오류가 발생하면 CMake 구성에서 QT_USE_TARGET_ANDROID_BUILD_DIR 을 비활성화하거나 Qt Creator 에서 Projects->Build Settings->CMake->Current Configuration 을 지웁니다.

Squish 라이선스가 없는 경우 무료 평가판을 받을 수 있습니다.

예제 프로젝트 @ code.qt.io

QML 애플리케이션도참조하세요 .

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.