Calqlatr

사용자 지정 구성 요소, 반응형 레이아웃, 애플리케이션 로직에 JavaScript를 사용하는 가로 및 세로 기기용으로 설계된 Qt Quick 앱입니다.

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

예제 실행하기

에서 예제를 실행하려면 Qt Creator에서 Welcome 모드를 열고 Examples 에서 예제를 선택합니다. 자세한 내용은 예제 빌드 및 실행을 참조하세요.

사용자 지정 컴포넌트 표시

Calqlatr 애플리케이션에서는 사용자 지정 유형이 사용됩니다. 이러한 유형은 별도의 .qml 파일에 정의됩니다:

  • 백스페이스 버튼.qml
  • 계산기 버튼.qml
  • Display.qml
  • NumberPad.qml

Main.qml 에서 이러한 사용자 정의 유형을 사용하려면 해당 유형이 있는 content 폴더에 가져오기 문을 추가합니다:

import "content"

예를 들어 NumberPad 유형은 Main.qml 에서 계산기의 숫자 패드를 만드는 데 사용됩니다. 이는 Qt Quick 에 있는 모든 시각적 항목의 기본 유형인 Item 유형 내에 중첩됩니다:

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

사용자 지정 컴포넌트는 모든 QML 파일에 정의할 수 있는 QML 유형으로, NumberPad.qml 과 같이 자체 .qml 파일에 정의된 컴포넌트와 동일하게 작동합니다. NumberPad.qml 에는 DigitButton 컴포넌트와 OperatorButton 컴포넌트가 정의되어 있습니다. 이러한 컴포넌트에서 새 속성을 추가하거나 기존 속성을 수정할 수 있습니다. 여기서는 두 사용자 정의 컴포넌트 모두에 대해 onReleased 핸들러를 덮어씁니다.

    component DigitButton: CalculatorButton {
        onReleased: {
            root.digitPressed(text)
            updateDimmed()
        }
    }

    component OperatorButton: CalculatorButton {
        onReleased: {
            root.operatorPressed(text)
            updateDimmed()
        }
        textColor: controller.qtGreenColor
        implicitWidth: 48
        dimmable: true
    }

또한 NumberPad 의 다른 버튼에는 CalculatorButton 유형을 사용합니다. CalculatorButton.qml 는 버튼의 기본 속성을 정의하며, NumberPad.qml 에서 각 인스턴스에 대해 수정합니다. 숫자 및 연산자 버튼의 경우 text, width, dimmable 속성과 같은 몇 가지 추가 속성이 추가됩니다. dimmable 을 사용하여 계산기 엔진이 해당 버튼의 입력을 받지 않을 때마다 버튼을 시각적으로 비활성화(흐리게 표시)할 수 있습니다.

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

content 디렉토리에 BackSpaceButton.qml 이라는 또 다른 파일이 있는데, 이는 텍스트를 사용하지 않고 버튼에 이미지를 렌더링하려는 CalculatorButton 의 특수한 경우입니다. 이 버튼은 OperatorButton 과 동일하지만 text 대신 icon 을 포함합니다:

    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"

    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

반응형 레이아웃

이 예시에서 반응형 레이아웃은 세로 모드와 가로 모드 모두에 대해 서로 다른 UI 구성 요소를 정렬합니다. 또한 이 두 모드 사이를 전환할 수 있습니다. Main.qml 에서 이를 확인할 수 있으며, 여기에는 세로 모드용 ColumnLayout 과 가로 모드용 RowLayout 이 정의되어 있습니다.

        ColumnLayout {
            id: portraitMode
            anchors.fill: parent
            visible: true

            LayoutItemProxy {
                target: display
                Layout.minimumHeight: display.minHeight
            }
            LayoutItemProxy {
                target: numberPad
                Layout.alignment: Qt.AlignHCenter
            }
        }

        RowLayout {
            id: landscapeMode
            anchors.fill: parent
            visible: false

            LayoutItemProxy {
                target: display
            }
            LayoutItemProxy {
                target: numberPad
                Layout.alignment: Qt.AlignVCenter
            }
        }

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

두 레이아웃 간의 토글은 isPortraitMode 속성에 대한 신호 핸들러에서 가시성을 설정하여 수행됩니다:

        onIsPortraitModeChanged: {
            if (isPortraitMode) {
                portraitMode.visible = true
                landscapeMode.visible = false
            } else {
                portraitMode.visible = false
                landscapeMode.visible = true
            }
        }

이는 QML이 모든 자체 선언된 프로퍼티(이 경우 on<Property>Changed 핸들러)에 대한 시그널 핸들러를 생성하기 때문에 가능하며, 여기서 <property>는 isPortraitMode 프로퍼티입니다.

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

        RowLayout {
            spacing: controller.spacing

            GridLayout {
                id: scientificGrid
                columns: 3
                columnSpacing: controller.spacing
                rowSpacing: controller.spacing
                visible: !isPortraitMode

                OperatorButton { text: "x²" }
                OperatorButton { text: "⅟x" }
                OperatorButton { text: "√" }
                OperatorButton { text: "x³" }
                OperatorButton { text: "sin" }
                OperatorButton { text: "|x|" }
                OperatorButton { text: "log" }
                OperatorButton { text: "cos" }
                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 {}
                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

이 경우 두 개의 LayoutItemProxy 항목이 생성됩니다. target 속성은 모든 과학 버튼을 포함하는 Grid 유형인 scientificGrid 과 모든 표준 버튼을 포함하는 또 다른 Grid 유형인 mainGrid 으로 설정됩니다.

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

        ...
        color: getBackgroundColor()
        border.color: getBorderColor()
    }

    contentItem: Text {
        text: button.text
        font.pixelSize: button.fontSize
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
        color: 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 = root.isButtonDisabled(mainGrid.children[i].text)
        }
        for (let j = 0; j < scientificGrid.children.length; j++){
            scientificGrid.children[j].dimmed = root.isButtonDisabled(scientificGrid.children[j].text)
        }
    }

계산 수행

계산기.js 파일은 계산기의 엔진을 정의합니다. 여기에는 계산기의 상태를 저장하는 변수와 사용자가 숫자 및 연산자 버튼을 누를 때 호출되는 함수가 포함되어 있습니다. 엔진을 사용하려면 별칭 CalcEngine 을 사용하여 calculator.js를 Main.qml 파일로 가져옵니다:

import "content/calculator.js" as CalcEngine

기본적으로 QML에서 JavaScript 파일을 가져오면 해당 파일의 새 인스턴스가 생성되며, 포함된 모든 상태는 해당 인스턴스에만 고유합니다. .pragma library 을 사용하면 스크립트의 모든 사용자가 상태를 공유할 수 있습니다.

.pragma library

사용자가 숫자를 누르면 해당 숫자의 텍스트가 디스플레이에 나타납니다. 연산자를 누르면 적절한 계산이 수행되고 등호(=) 연산자를 사용하여 결과를 표시할 수 있습니다. 올클리어(AC) 연산자는 계산기 엔진을 초기화합니다.

파일 목록

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

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

© 2025 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.