diff --git a/.cmake/QmlPlugin.cmake b/.cmake/QmlPlugin.cmake new file mode 100644 index 00000000..29aec2fc --- /dev/null +++ b/.cmake/QmlPlugin.cmake @@ -0,0 +1,139 @@ +include(CMakeParseArguments) +find_package(Qt5 REQUIRED COMPONENTS Core) + +function(FindQmlPluginDump) + get_target_property (QT_QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION) + execute_process( + COMMAND ${QT_QMAKE_EXECUTABLE} -query QT_INSTALL_BINS + OUTPUT_VARIABLE QT_BIN_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +endfunction() + +function(FindQtInstallQml) + execute_process( + COMMAND ${QT_QMAKE_EXECUTABLE} -query QT_INSTALL_QML + OUTPUT_VARIABLE PROC_RESULT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +set(QT_INSTALL_QML ${PROC_RESULT} PARENT_SCOPE) +endfunction() + +function(add_qmlplugin TARGET) + set(options NO_AUTORCC NO_AUTOMOC) + set(oneValueArgs URI VERSION BINARY_DIR QMLDIR LIBTYPE) + set(multiValueArgs SOURCES QMLFILES QMLFILESALIAS) + cmake_parse_arguments(QMLPLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if(NOT QMLPLUGIN_URI OR NOT QMLPLUGIN_VERSION OR NOT QMLPLUGIN_QMLDIR OR NOT QMLPLUGIN_LIBTYPE) + message(WARNING "TARGET,URI,VERSION,qmldir and LIBTYPE must be set, no files generated") + return() + endif() + if(NOT QMLPLUGIN_BINARY_DIR) + set(QMLPLUGIN_BINARY_DIR ${CMAKE_BINARY_DIR}/${QMLPLUGIN_URI}) + endif() + add_library(${TARGET} ${QMLPLUGIN_LIBTYPE} + ${QMLPLUGIN_SOURCES} + ) +set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/lib) +add_custom_target("${TARGET}-qmlfiles" SOURCES ${QMLPLUGIN_QMLFILES}) + +if(QMLPLUGIN_NO_AUTORCC) + set_target_properties(${TARGET} PROPERTIES AUTOMOC OFF) +else() + set_target_properties(${TARGET} PROPERTIES AUTOMOC ON) +endif() +if(QMLPLUGIN_NO_AUTOMOC) + set_target_properties(${TARGET} PROPERTIES AUTOMOC OFF) +else() + set_target_properties(${TARGET} PROPERTIES AUTOMOC ON) +endif() + +if (${QMLPLUGIN_LIBTYPE} MATCHES "SHARED") + FindQmlPluginDump() + FindQtInstallQml() + if(QMLPLUGIN_BINARY_DIR) + set(MAKE_QMLPLUGINDIR_COMMAND ${CMAKE_COMMAND} -E make_directory ${QMLPLUGIN_BINARY_DIR}) + endif() + set(COPY_QMLDIR_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/Qt5/${QMLPLUGIN_QMLDIR}/qmldir $/${QMLPLUGIN_URI}/qmldir) + set(INSTALL_QMLDIR_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/Qt5/${QMLPLUGIN_QMLDIR}/qmldir ${QMLPLUGIN_BINARY_DIR}/qmldir) + set(COPY_QMLTYPES_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/Qt5/${QMLPLUGIN_QMLDIR}/plugins.qmltypes $/${QMLPLUGIN_URI}/plugins.qmltypes) + set(INSTALL_QMLTYPES_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/Qt5/${QMLPLUGIN_QMLDIR}/plugins.qmltypes ${QMLPLUGIN_BINARY_DIR}/plugins.qmltypes) + set(COPY_LIBRARY_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/$ $/${QMLPLUGIN_URI}) + set(INSTALL_LIBRARY_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/$ ${QMLPLUGIN_BINARY_DIR}) + if(QMLPLUGIN_QMLDIR) + set(COPY_QMLFILES_COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/Qt5/${QMLPLUGIN_QMLDIR} $/${QMLPLUGIN_URI}) + else() + set(COPY_QMLFILES_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/${QMLPLUGIN_QMLFILES} $/${QMLPLUGIN_URI}) + endif() + set(INSTALL_QMLFILES_COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/Qt5/${QMLPLUGIN_QMLDIR} ${QMLPLUGIN_BINARY_DIR}) + if(QMLPLUGIN_BINARY_DIR) + add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${MAKE_QMLPLUGINDIR_COMMAND} + COMMAND ${COPY_QMLDIR_COMMAND} + COMMENT "Copying qmldir to binary directory" + ) +else() + add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${COPY_QMLDIR_COMMAND} + COMMENT "Copying qmldir to binary directory" + ) +endif() + +if(QMLPLUGIN_BINARY_DIR) + add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${MAKE_QMLPLUGINDIR_COMMAND} + COMMAND ${COPY_QMLTYPES_COMMAND} + COMMENT "Copying qmltypes to binary directory" + ) +else() + add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${COPY_QMLTYPES_COMMAND} + COMMENT "Copying qmltypes to binary directory" + ) +endif() + +add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${COPY_LIBRARY_COMMAND} + COMMENT "Copying Lib to binary plugin directory" +) + +if(QMLPLUGIN_QMLFILES) + add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${COPY_QMLFILES_COMMAND} + COMMENT "Copying QML files to binary directory" + ) +endif() + +add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${GENERATE_QMLTYPES_COMMAND} + COMMENT "Generating plugin.qmltypes" +) + +string(REPLACE "." "/" QMLPLUGIN_INSTALL_URI ${QMLPLUGIN_URI}) + +add_custom_command( + TARGET ${TARGET} + POST_BUILD + COMMAND ${INSTALL_QMLTYPES_COMMAND} + COMMAND ${INSTALL_QMLDIR_COMMAND} + COMMAND ${INSTALL_LIBRARY_COMMAND} + COMMAND ${INSTALL_QMLFILES_COMMAND} + COMMAND ${INSTALL_QMLTYPES_COMMAND} + COMMENT "Install library and aditional files" +) +endif() +endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt index 703b6fcf..59421d76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,9 +11,10 @@ option(FLUENTUI_BUILD_EXAMPLES "Build FluentUI demo applications." ON) option(FLUENTUI_BUILD_FRAMELESSHEPLER "Build FramelessHelper." ON) option(FLUENTUI_BUILD_STATIC_LIB "Build static library." OFF) -find_package(Qt6 REQUIRED COMPONENTS Core) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) -set(QT_SDK_DIR "${Qt6_DIR}") +set(QT_SDK_DIR "${Qt${QT_VERSION_MAJOR}_DIR}") cmake_path(GET QT_SDK_DIR PARENT_PATH QT_SDK_DIR) cmake_path(GET QT_SDK_DIR PARENT_PATH QT_SDK_DIR) cmake_path(GET QT_SDK_DIR PARENT_PATH QT_SDK_DIR) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index ccf0f1a7..57e86631 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -26,7 +26,8 @@ endif() #获取文件路径分隔符(解决执行命令的时候有些平台会报错) file(TO_CMAKE_PATH "/" PATH_SEPARATOR) -find_package(Qt6 REQUIRED COMPONENTS Quick Svg Network) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Quick Svg Network) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Quick Svg Network) if(QT_VERSION VERSION_GREATER_EQUAL "6.3") qt_standard_project_setup() @@ -50,19 +51,25 @@ foreach(filepath ${CPP_FILES}) list(APPEND sources_files ${filename}) endforeach(filepath) -#遍历所有qml文件 -file(GLOB_RECURSE QML_PATHS *.qml) -foreach(filepath ${QML_PATHS}) - string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" filename ${filepath}) - list(APPEND qml_files ${filename}) -endforeach(filepath) +if(QT_VERSION VERSION_GREATER_EQUAL "6.2") + #遍历所有qml文件 + file(GLOB_RECURSE QML_PATHS *.qml qmldir) + foreach(filepath ${QML_PATHS}) + string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" filename ${filepath}) + if(${filepath} MATCHES "Qt${QT_VERSION_MAJOR}/") + string(REPLACE "qml-Qt${QT_VERSION_MAJOR}" "qml" filealias ${filename}) + set_source_files_properties(${filename} PROPERTIES QT_RESOURCE_ALIAS ${filealias}) + list(APPEND qml_files ${filename}) + endif() + endforeach(filepath) -#遍历所有资源文件 -file(GLOB_RECURSE RES_PATHS *.png *.jpg *.svg *.ico *.ttf *.webp qmldir) -foreach(filepath ${RES_PATHS}) - string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" filename ${filepath}) - list(APPEND resource_files ${filename}) -endforeach(filepath) + #遍历所有资源文件 + file(GLOB_RECURSE RES_PATHS *.png *.jpg *.svg *.ico *.ttf *.webp) + foreach(filepath ${RES_PATHS}) + string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" filename ${filepath}) + list(APPEND resource_files ${filename}) + endforeach(filepath) +endif() #如果是Windows平台,则生成rc文件 set(EXAMPLE_VERSION_RC_PATH "") @@ -76,12 +83,12 @@ endif() #添加可执行文件 if (WIN32) - qt_add_executable(example + add_executable(example ${sources_files} ${EXAMPLE_VERSION_RC_PATH} ) else () - qt_add_executable(example + add_executable(example ${sources_files} ) endif () @@ -102,14 +109,21 @@ if(WIN32) ) endif() -#添加qml模块 -qt_add_qml_module(example - URI "example" - VERSION 1.0 - QML_FILES ${qml_files} - RESOURCES ${resource_files} - RESOURCE_PREFIX "/" -) +if(QT_VERSION VERSION_GREATER_EQUAL "6.2") + #添加qml模块 + qt_add_qml_module(example + URI "example" + VERSION 1.0 + QML_FILES ${qml_files} + RESOURCES ${resource_files} + RESOURCE_PREFIX "/" + ) +else() + target_include_directories(example PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ) + target_sources(example PRIVATE resource.qrc) +endif() #导入component头文件,不然通过QML_NAMED_ELEMENT生成的c++类会找不到头文件报错 target_include_directories(example PRIVATE @@ -128,9 +142,9 @@ set_target_properties(example PROPERTIES #链接库 if (FLUENTUI_BUILD_STATIC_LIB) target_link_libraries(example PRIVATE - Qt::Quick - Qt::Svg - Qt::Network + Qt${QT_VERSION_MAJOR}::Quick + Qt${QT_VERSION_MAJOR}::Svg + Qt${QT_VERSION_MAJOR}::Network fluentui fluentuiplugin FramelessHelper::Core @@ -138,9 +152,9 @@ if (FLUENTUI_BUILD_STATIC_LIB) ) else() target_link_libraries(example PRIVATE - Qt::Quick - Qt::Svg - Qt::Network + Qt${QT_VERSION_MAJOR}::Quick + Qt${QT_VERSION_MAJOR}::Svg + Qt${QT_VERSION_MAJOR}::Network fluentuiplugin FramelessHelper::Core FramelessHelper::Quick diff --git a/example/qml-Qt6/App.qml b/example/qml-Qt6/App.qml new file mode 100644 index 00000000..f0af5371 --- /dev/null +++ b/example/qml-Qt6/App.qml @@ -0,0 +1,44 @@ +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Layouts +import FluentUI + +Window { + id: app + flags: Qt.SplashScreen + + FluHttpInterceptor{ + id:interceptor + function onIntercept(request){ + if(request.method === "get"){ + request.params["method"] = "get" + } + if(request.method === "post"){ + request.params["method"] = "post" + } + request.headers["token"] ="yyds" + request.headers["os"] ="pc" + console.debug(JSON.stringify(request)) + return request + } + } + + Component.onCompleted: { + FluApp.init(app) + FluTheme.darkMode = FluThemeType.System + FluTheme.enableAnimation = true + FluApp.routes = { + "/":"qrc:/example/qml/window/MainWindow.qml", + "/about":"qrc:/example/qml/window/AboutWindow.qml", + "/login":"qrc:/example/qml/window/LoginWindow.qml", + "/hotload":"qrc:/example/qml/window/HotloadWindow.qml", + "/singleTaskWindow":"qrc:/example/qml/window/SingleTaskWindow.qml", + "/standardWindow":"qrc:/example/qml/window/StandardWindow.qml", + "/singleInstanceWindow":"qrc:/example/qml/window/SingleInstanceWindow.qml" + } + FluApp.initialRoute = "/" + FluApp.httpInterceptor = interceptor + FluApp.run() + } +} diff --git a/example/qml-Qt6/component/CodeExpander.qml b/example/qml-Qt6/component/CodeExpander.qml new file mode 100644 index 00000000..f69c879a --- /dev/null +++ b/example/qml-Qt6/component/CodeExpander.qml @@ -0,0 +1,145 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI + +FluExpander{ + + id:control + property string code: "" + headerText: "Source" + contentHeight:content.height + focus: false + + FluMultilineTextBox{ + id:content + width:parent.width + activeFocusOnTab: false + activeFocusOnPress: false + readOnly: true + text:highlightQmlCode(code) + textFormat: FluMultilineTextBox.RichText + KeyNavigation.priority: KeyNavigation.BeforeItem + background:Rectangle{ + radius: 4 + color:FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(247/255,247/255,247/255,1) + border.color: FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(226/255,229/255,234/255,1) + border.width: 1 + } + } + + FluIconButton{ + iconSource:FluentIcons.Copy + anchors{ + right: parent.right + top: parent.top + rightMargin: 5 + topMargin: 5 + } + onClicked:{ + FluTools.clipText(FluTools.html2PlantText(content.text)) + showSuccess("复制成功") + } + } + + function htmlEncode(e){ + var i,s; + for(i in s={ + "&":/&/g,//""//":/"/g,"'":/'/g, + "<":/":/>/g,"
":/\n/g, + " ":/ /g," ":/\t/g + })e=e.replace(s[i],i); + return e; + } + + function highlightQmlCode(code) { + // 定义 QML 关键字列表 + var qmlKeywords = [ + "FluTextButton", + "FluAppBar", + "FluAutoSuggestBox", + "FluBadge", + "FluButton", + "FluCalendarPicker", + "FluCalendarView", + "FluCarousel", + "FluCheckBox", + "FluColorPicker", + "FluColorView", + "FluComboBox", + "FluContentDialog", + "FluContentPage", + "FluDatePicker", + "FluDivider", + "FluDropDownButton", + "FluExpander", + "FluFilledButton", + "FluFlipView", + "FluFocusRectangle", + "FluIcon", + "FluIconButton", + "FluInfoBar", + "FluItem", + "FluMediaPlayer", + "FluMenu", + "FluMenuItem", + "FluMultilineTextBox", + "FluNavigationView", + "FluObject", + "FluPaneItem", + "FluPaneItemExpander", + "FluPaneItemHeader", + "FluPaneItemSeparator", + "FluPivot", + "FluPivotItem", + "FluProgressBar", + "FluProgressRing", + "FluRadioButton", + "FluRectangle", + "FluScrollablePage", + "FluScrollBar", + "FluShadow", + "FluSlider", + "FluTabView", + "FluText", + "FluTextArea", + "FluTextBox", + "FluTextBoxBackground", + "FluTextBoxMenu", + "FluTextButton", + "FluTextFiled", + "FluTimePicker", + "FluToggleSwitch", + "FluTooltip", + "FluTreeView", + "FluWindow", + "FluWindowResize", + "FluToggleButton", + "FluTableView", + "FluColors", + "FluTheme", + "FluStatusView", + "FluRatingControl", + "FluPasswordBox", + "FluBreadcrumbBar", + "FluCopyableText", + "FluAcrylic", + "FluRemoteLoader", + "FluMenuBar", + "FluPagination", + "FluRadioButtons", + "FluImage", + "FluSpinBox", + "FluHttp", + "FluWatermark", + "FluTour", + "FluQRCode", + "FluTimeline", + "FluChart" + ]; + code = code.replace(/\n/g, "
"); + code = code.replace(/ /g, " "); + return code.replace(RegExp("\\b(" + qmlKeywords.join("|") + ")\\b", "g"), "$1"); + } +} diff --git a/example/qml-Qt6/component/CustomWindow.qml b/example/qml-Qt6/component/CustomWindow.qml new file mode 100644 index 00000000..88451d28 --- /dev/null +++ b/example/qml-Qt6/component/CustomWindow.qml @@ -0,0 +1,64 @@ +import QtQuick +import QtQuick.Layouts +import FluentUI +import org.wangwenx190.FramelessHelper + +FluWindow { + id:window + property bool fixSize + property alias titleVisible: title_bar.titleVisible + property bool appBarVisible: true + default property alias content: container.data + FluAppBar { + id: title_bar + title: window.title + visible: window.appBarVisible + icon:"qrc:/example/res/image/favicon.ico" + anchors { + top: parent.top + left: parent.left + right: parent.right + } + darkText: lang.dark_mode + } + Item{ + id:container + anchors{ + top: title_bar.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } + clip: true + } + FramelessHelper{ + id:framless_helper + onReady: { + setTitleBarItem(title_bar) + moveWindowToDesktopCenter() + setHitTestVisible(title_bar.minimizeButton()) + setHitTestVisible(title_bar.maximizeButton()) + setHitTestVisible(title_bar.closeButton()) + setWindowFixedSize(fixSize) + title_bar.maximizeButton.visible = !fixSize + if (blurBehindWindowEnabled) + window.background = undefined + window.show() + } + } + Connections{ + target: FluTheme + function onDarkChanged(){ + if (FluTheme.dark) + FramelessUtils.systemTheme = FramelessHelperConstants.Dark + else + FramelessUtils.systemTheme = FramelessHelperConstants.Light + } + } + function setHitTestVisible(com){ + framless_helper.setHitTestVisible(com) + } + function setTitleBarItem(com){ + framless_helper.setTitleBarItem(com) + } +} diff --git a/example/qml-Qt6/global/ItemsFooter.qml b/example/qml-Qt6/global/ItemsFooter.qml new file mode 100644 index 00000000..c6742d06 --- /dev/null +++ b/example/qml-Qt6/global/ItemsFooter.qml @@ -0,0 +1,30 @@ +pragma Singleton + +import QtQuick +import FluentUI + +FluObject{ + + property var navigationView + + id:footer_items + + FluPaneItemSeparator{} + + FluPaneItem{ + title:lang.about + icon:FluentIcons.Contact + tapFunc:function(){ + FluApp.navigate("/about") + } + } + + FluPaneItem{ + title:lang.settings + icon:FluentIcons.Settings + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Settings.qml") + } + } + +} diff --git a/example/qml-Qt6/global/ItemsOriginal.qml b/example/qml-Qt6/global/ItemsOriginal.qml new file mode 100644 index 00000000..3f3beb48 --- /dev/null +++ b/example/qml-Qt6/global/ItemsOriginal.qml @@ -0,0 +1,483 @@ +pragma Singleton + +import QtQuick +import FluentUI + +FluObject{ + + property var navigationView + + function rename(item, newName){ + if(newName && newName.trim().length>0){ + item.title = newName; + } + } + + FluPaneItem{ + id:item_home + count: 9 + title:lang.home + infoBadge:FluBadge{ + count: item_home.count + } + icon:FluentIcons.Home + onTap:{ + if(navigationView.getCurrentUrl()){ + item_home.count = 0 + } + navigationView.push("qrc:/example/qml/page/T_Home.qml") + } + editDelegate: FluTextBox{ + text:item_home.title + } + menuDelegate: FluMenu{ + id:nav_item_right_menu + width: 120 + FluMenuItem{ + text: "重命名" + visible: true + onClicked: { + item_home.showEdit = true + } + } + } + } + + FluPaneItemExpander{ + id:item_expander_basic_input + title:lang.basic_input + icon:FluentIcons.CheckboxComposite + editDelegate: FluTextBox{ + text:item_expander_basic_input.title + } + menuDelegate: FluMenu{ + FluMenuItem{ + text: "重命名" + visible: true + onClicked: { + item_expander_basic_input.showEdit = true + } + } + } + FluPaneItem{ + id:item_buttons + count: 99 + infoBadge:FluBadge{ + count: item_buttons.count + } + title:"Buttons" + image:"qrc:/example/res/image/control/Button.png" + recentlyUpdated:true + desc:"A control that responds to user input and raisesa Click event." + onTap:{ + item_buttons.count = 0 + navigationView.push("qrc:/example/qml/page/T_Buttons.qml") + } + } + FluPaneItem{ + id:item_text + title:"Text" + count: 5 + infoBadge:FluBadge{ + count: item_text.count + color: Qt.rgba(82/255,196/255,26/255,1) + } + onTap:{ + item_text.count = 0 + navigationView.push("qrc:/example/qml/page/T_Text.qml") + } + } + FluPaneItem{ + title:"Image" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Image.qml") + } + } + FluPaneItem{ + title:"Slider" + image:"qrc:/example/res/image/control/Slider.png" + recentlyUpdated:true + desc:"A control that lets the user select from a rangeof values by moving a Thumb control along atrack." + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Slider.qml") + } + } + FluPaneItem{ + title:"CheckBox" + image:"qrc:/example/res/image/control/Checkbox.png" + recentlyUpdated:true + desc:"A control that a user can select or clear." + onTap:{ + navigationView.push("qrc:/example/qml/page/T_CheckBox.qml") + } + } + FluPaneItem{ + title:"RadioButton" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_RadioButton.qml") + } + } + FluPaneItem{ + title:"ToggleSwitch" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_ToggleSwitch.qml") + } + } + } + + FluPaneItemExpander{ + title:lang.form + icon:FluentIcons.GridView + FluPaneItem{ + title:"TextBox" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_TextBox.qml") + } + } + FluPaneItem{ + title:"TimePicker" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_TimePicker.qml") + } + } + FluPaneItem{ + title:"DatePicker" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_DatePicker.qml") + } + } + FluPaneItem{ + title:"CalendarPicker" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_CalendarPicker.qml") + } + } + FluPaneItem{ + title:"ColorPicker" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_ColorPicker.qml") + } + } + } + + FluPaneItemExpander{ + title:lang.surface + icon:FluentIcons.SurfaceHub + FluPaneItem{ + title:"InfoBar" + image:"qrc:/example/res/image/control/InfoBar.png" + recentlyUpdated:true + desc:"An inline message to display app-wide statuschange information." + onTap:{ + navigationView.push("qrc:/example/qml/page/T_InfoBar.qml") + } + } + FluPaneItem{ + title:"Progress" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Progress.qml") + } + } + FluPaneItem{ + title:"RatingControl" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_RatingControl.qml") + } + } + FluPaneItem{ + title:"Badge" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Badge.qml") + } + } + FluPaneItem{ + title:"Rectangle" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Rectangle.qml") + } + } + FluPaneItem{ + title:"StatusView" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_StatusView.qml") + } + } + FluPaneItem{ + title:"Carousel" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Carousel.qml") + } + } + FluPaneItem{ + title:"Expander" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Expander.qml") + } + } + FluPaneItem{ + title:"Watermark" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Watermark.qml") + } + } + } + + FluPaneItemExpander{ + title:lang.popus + icon:FluentIcons.ButtonMenu + FluPaneItem{ + title:"Dialog" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Dialog.qml") + } + } + FluPaneItem{ + id:item_combobox + title:"ComboBox" + count: 9 + infoBadge:FluBadge{ + count: item_combobox.count + color: Qt.rgba(250/255,173/255,20/255,1) + } + onTap:{ + item_combobox.count = 0 + navigationView.push("qrc:/example/qml/page/T_ComboBox.qml") + } + } + FluPaneItem{ + title:"Tooltip" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Tooltip.qml") + } + } + FluPaneItem{ + title:"Menu" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Menu.qml") + } + } + } + + FluPaneItemExpander{ + title:lang.navigation + icon:FluentIcons.AllApps + FluPaneItem{ + title:"Pivot" + image:"qrc:/example/res/image/control/Pivot.png" + recentlyAdded:true + order:3 + desc:"Presents information from different sources in atabbed view." + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Pivot.qml") + } + } + FluPaneItem{ + title:"BreadcrumbBar" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_BreadcrumbBar.qml") + } + } + FluPaneItem{ + title:"TabView" + image:"qrc:/example/res/image/control/TabView.png" + recentlyAdded:true + order:1 + desc:"A control that displays a collection of tabs thatcan be used to display several documents." + onTap:{ + navigationView.push("qrc:/example/qml/page/T_TabView.qml") + } + } + FluPaneItem{ + title:"TreeView" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_TreeView.qml") + } + } + FluPaneItem{ + title:"TableView" + image:"qrc:/example/res/image/control/DataGrid.png" + recentlyAdded:true + order:4 + desc:"The TableView control provides a flexible way to display a collection of data in rows and columns" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_TableView.qml") + } + } + FluPaneItem{ + title:"Pagination" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Pagination.qml") + } + } + FluPaneItem{ + title:"MultiWindow" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_MultiWindow.qml") + } + } + FluPaneItem{ + title:"FlipView" + image:"qrc:/example/res/image/control/FlipView.png" + recentlyAdded:true + order:2 + desc:"Presents a collection of items that the user canflip through, one item at a time." + onTap:{ + navigationView.push("qrc:/example/qml/page/T_FlipView.qml") + } + } + } + + FluPaneItemExpander{ + title:lang.theming + icon:FluentIcons.Brightness + FluPaneItem{ + title:"Acrylic" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Acrylic.qml") + } + } + FluPaneItem{ + title:"Theme" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Theme.qml") + } + } + FluPaneItem{ + title:"Typography" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Typography.qml") + } + } + FluPaneItem{ + title:"Awesome" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Awesome.qml") + } + } + } + + FluPaneItemSeparator{ + spacing:10 + size:1 + } + + FluPaneItemExpander{ + title:lang.other + icon:FluentIcons.Shop + FluPaneItem{ + title:"QRCode" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_QRCode.qml") + } + } + FluPaneItem{ + title:"Tour" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Tour.qml") + } + } + FluPaneItem{ + title:"Timeline" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Timeline.qml") + } + } + FluPaneItem{ + title:"Screenshot" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Screenshot.qml") + } + } + FluPaneItem{ + title:"Captcha" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Captcha.qml") + } + } + FluPaneItem{ + title:"Chart" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Chart.qml") + } + } + FluPaneItem{ + title:"Http" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Http.qml") + } + } + FluPaneItem{ + id:item_other + title:"RemoteLoader" + count: 99 + infoBadge:FluBadge{ + count: item_other.count + color: Qt.rgba(82/255,196/255,26/255,1) + } + onTap:{ + item_other.count = 0 + navigationView.push("qrc:/example/qml/page/T_RemoteLoader.qml") + } + } + FluPaneItem{ + title:"HotLoader" + tapFunc:function(){ + FluApp.navigate("/hotload") + } + } + } + + function getRecentlyAddedData(){ + var arr = [] + for(var i=0;i ${item.title}`,key:item.key}) + } + else + arr.push({title:item.title,key:item.key}) + } + } + return arr + } + + function startPageByItem(data){ + navigationView.startPageByItem(data) + } + +} diff --git a/example/qml-Qt6/global/MainEvent.qml b/example/qml-Qt6/global/MainEvent.qml new file mode 100644 index 00000000..7119ffc6 --- /dev/null +++ b/example/qml-Qt6/global/MainEvent.qml @@ -0,0 +1,9 @@ +pragma Singleton + +import QtQuick +import QtQuick.Controls +import FluentUI + +QtObject { + property int displayMode : FluNavigationViewType.Auto +} diff --git a/example/qml-Qt6/global/qmldir b/example/qml-Qt6/global/qmldir new file mode 100644 index 00000000..a1122801 --- /dev/null +++ b/example/qml-Qt6/global/qmldir @@ -0,0 +1,3 @@ +singleton ItemsOriginal 1.0 ItemsOriginal.qml +singleton ItemsFooter 1.0 ItemsFooter.qml +singleton MainEvent 1.0 MainEvent.qml diff --git a/example/qml-Qt6/page/T_Acrylic.qml b/example/qml-Qt6/page/T_Acrylic.qml new file mode 100644 index 00000000..ae83a596 --- /dev/null +++ b/example/qml-Qt6/page/T_Acrylic.qml @@ -0,0 +1,114 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Acrylic" + + RowLayout{ + spacing: 10 + Layout.topMargin: 20 + FluText{ + text:"tintColor:" + Layout.alignment: Qt.AlignVCenter + } + FluColorPicker{ + id:color_picker + } + } + RowLayout{ + spacing: 10 + FluText{ + text:"tintOpacity:" + Layout.alignment: Qt.AlignVCenter + } + FluSlider{ + id:slider_tint_opacity + value: 65 + } + } + RowLayout{ + spacing: 10 + FluText{ + text:"blurRadius:" + Layout.alignment: Qt.AlignVCenter + } + FluSlider{ + id:slider_blur_radius + value: 32 + } + } + FluArea{ + Layout.fillWidth: true + height: 1200/4+20 + paddings: 10 + Layout.topMargin: 10 + FluRectangle{ + width: 1920/4 + height: 1200/4 + radius:[15,15,15,15] + Image { + id:image + asynchronous: true + source: "qrc:/example/res/image/bg_scenic.png" + anchors.fill: parent + sourceSize: Qt.size(2*width,2*height) + } + FluAcrylic { + id:acrylic + target: image + width: 200 + height: 200 + tintOpacity: slider_tint_opacity.value/100 + tintColor: color_picker.colorValue + blurRadius: slider_blur_radius.value + x:(image.width-width)/2 + y:(image.height-height)/2 + FluText { + anchors.centerIn: parent + text: "Acrylic" + color: "#FFFFFF" + font.bold: true + } + MouseArea { + property point clickPos: Qt.point(0,0) + id:drag_area + anchors.fill: parent + onPressed: (mouse)=>{ + clickPos = Qt.point(mouse.x, mouse.y) + } + onPositionChanged: (mouse)=>{ + var delta = Qt.point(mouse.x - clickPos.x,mouse.y - clickPos.y) + acrylic.x = acrylic.x + delta.x + acrylic.y = acrylic.y + delta.y + } + } + } + Layout.topMargin: 20 + } + + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'Image{ + id:image + width: 800 + height: 600 + source: "qrc:/example/res/image/image_huoyin.webp" + radius: 8 + } + FluAcrylic{ + target:image + width: 100 + height: 100 + anchors.centerIn: parent + } +}' + } + +} diff --git a/example/qml-Qt6/page/T_Awesome.qml b/example/qml-Qt6/page/T_Awesome.qml new file mode 100644 index 00000000..182ce910 --- /dev/null +++ b/example/qml-Qt6/page/T_Awesome.qml @@ -0,0 +1,71 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI + +FluContentPage { + + title:"Awesome" + + FluTextBox{ + id:text_box + placeholderText: "请输入关键字" + anchors{ + topMargin: 20 + top:parent.top + } + } + + FluFilledButton{ + text:"搜索" + anchors{ + left: text_box.right + verticalCenter: text_box.verticalCenter + leftMargin: 14 + } + onClicked: { + grid_view.model = FluApp.awesomelist(text_box.text) + } + } + GridView{ + id:grid_view + cellWidth: 80 + cellHeight: 80 + clip: true + boundsBehavior: GridView.StopAtBounds + model:FluApp.awesomelist() + ScrollBar.vertical: FluScrollBar {} + anchors{ + topMargin: 10 + top:text_box.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } + delegate: Item { + width: 68 + height: 80 + FluIconButton{ + id:item_icon + iconSource:modelData.icon + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + var text ="FluentIcons."+modelData.name; + FluTools.clipText(text) + showSuccess("您复制了 "+text) + } + } + FluText { + id:item_name + font.pixelSize: 10 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: item_icon.bottom + width:parent.width + wrapMode: Text.WrapAnywhere + text: modelData.name + horizontalAlignment: Text.AlignHCenter + } + } + } +} diff --git a/example/qml-Qt6/page/T_Badge.qml b/example/qml-Qt6/page/T_Badge.qml new file mode 100644 index 00000000..baccc3d7 --- /dev/null +++ b/example/qml-Qt6/page/T_Badge.qml @@ -0,0 +1,129 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Badge" + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 106 + paddings: 10 + + Column{ + spacing: 15 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluText{ + text:"一般出现在通知图标或头像的右上角,用于显示需要处理的消息条数" + } + + Row{ + spacing: 20 + Rectangle{ + width: 40 + height: 40 + radius: 8 + color: Qt.rgba(191/255,191/255,191/255,1) + FluBadge{ + topRight: true + showZero: true + count:0 + } + } + + Rectangle{ + width: 40 + height: 40 + radius: 8 + color: Qt.rgba(191/255,191/255,191/255,1) + FluBadge{ + topRight: true + showZero: true + count:5 + } + } + Rectangle{ + width: 40 + height: 40 + radius: 8 + color: Qt.rgba(191/255,191/255,191/255,1) + FluBadge{ + topRight: true + showZero: true + count:50 + } + } + Rectangle{ + width: 40 + height: 40 + radius: 8 + color: Qt.rgba(191/255,191/255,191/255,1) + FluBadge{ + topRight: true + showZero: true + count:100 + } + } + Rectangle{ + width: 40 + height: 40 + radius: 8 + color: Qt.rgba(191/255,191/255,191/255,1) + FluBadge{ + topRight: true + showZero: true + isDot:true + } + } + Rectangle{ + width: 40 + height: 40 + radius: 8 + color: Qt.rgba(191/255,191/255,191/255,1) + FluBadge{ + topRight: true + showZero: true + count:99 + color: Qt.rgba(250/255,173/255,20/255,1) + } + } + Rectangle{ + width: 40 + height: 40 + radius: 8 + color: Qt.rgba(191/255,191/255,191/255,1) + FluBadge{ + topRight: true + showZero: true + count:99 + color: Qt.rgba(82/255,196/255,26/255,1) + } + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'Rectangle{ + width: 40 + height: 40 + radius: 8 + color: Qt.rgba(191/255,191/255,191/255,1) + FluBadge{ + count: 100 + isDot: false + color: Qt.rgba(82/255,196/255,26/255,1) + } +}' + } + +} diff --git a/example/qml-Qt6/page/T_BreadcrumbBar.qml b/example/qml-Qt6/page/T_BreadcrumbBar.qml new file mode 100644 index 00000000..f6a1ba08 --- /dev/null +++ b/example/qml-Qt6/page/T_BreadcrumbBar.qml @@ -0,0 +1,94 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"BreadcurmbBar" + + Component.onCompleted: { + var items = [] + for(var i=0;i<10;i++){ + items.push({title:"Item_"+(i+1)}) + } + breadcrumb_1.items = items + breadcrumb_2.items = items + } + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + + FluBreadcrumbBar{ + id:breadcrumb_1 + width:parent.width + anchors.verticalCenter: parent.verticalCenter + onClickItem: + (model)=>{ + showSuccess(model.title) + } + } + } + + + FluArea{ + Layout.fillWidth: true + height: 100 + paddings: 10 + Layout.topMargin: 20 + + ColumnLayout{ + anchors.verticalCenter: parent.verticalCenter + width:parent.width + spacing: 10 + + FluFilledButton{ + text:"Reset sample" + onClicked:{ + var items = [] + for(var i=0;i<10;i++){ + items.push({title:"Item_"+(i+1)}) + } + breadcrumb_2.items = items + } + } + + FluBreadcrumbBar{ + id:breadcrumb_2 + separator:">" + spacing:8 + textSize:18 + Layout.fillWidth: true + onClickItem: + (model)=>{ + //不是点击最后一个item元素 + if(model.index+1!==count()){ + breadcrumb_2.remove(model.index+1,count()-model.index-1) + } + showSuccess(model.title) + } + } + } + } + + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluBreadcrumbBar{ + width:parent.width + separator:">" + spacing:8 + textSize:18 + onClickItem: (model)=>{ + + } +}' + } + + +} diff --git a/example/qml-Qt6/page/T_Buttons.qml b/example/qml-Qt6/page/T_Buttons.qml new file mode 100644 index 00000000..7707333f --- /dev/null +++ b/example/qml-Qt6/page/T_Buttons.qml @@ -0,0 +1,348 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Controls.Basic +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Buttons" + + FluText{ + Layout.topMargin: 20 + text:"支持Tab键切换焦点,空格键执行点击事件" + } + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + + FluTextButton{ + disabled:text_button_switch.checked + text:"Text Button" + contentDescription: "文本按钮" + onClicked: { + showInfo("点击Text Button") + } + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + } + FluToggleSwitch{ + id:text_button_switch + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluTextButton{ + text:"Text Button" + onClicked: { + + } +}' + } + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + + FluButton{ + disabled:button_switch.checked + text:"Standard Button" + onClicked: { + showInfo("点击StandardButton") + } + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + } + FluToggleSwitch{ + id:button_switch + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluButton{ + text:"Standard Button" + onClicked: { + + } +}' + } + + FluArea{ + Layout.fillWidth: true + height: 68 + Layout.topMargin: 20 + paddings: 10 + + FluFilledButton{ + disabled:filled_button_switch.checked + text:"Filled Button" + onClicked: { + showWarning("点击FilledButton"+height) + } + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + } + FluToggleSwitch{ + id:filled_button_switch + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluFilledButton{ + text:"Filled Button" + onClicked: { + + } +}' + } + + FluArea{ + Layout.fillWidth: true + height: 68 + Layout.topMargin: 20 + paddings: 10 + + FluToggleButton{ + disabled:toggle_button_switch.checked + text:"Toggle Button" + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + } + FluToggleSwitch{ + id:toggle_button_switch + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluToggleButton{ + text:"Toggle Button" + onClicked: { + checked = !checked + } +}' + } + + + FluArea{ + Layout.fillWidth: true + height: layout_icon_button.height + 30 + paddings: 10 + Layout.topMargin: 20 + Flow{ + id:layout_icon_button + spacing: 10 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + right: icon_button_switch.left + } + FluIconButton{ + disabled:icon_button_switch.checked + iconDelegate: Image{ sourceSize: Qt.size(40,40) ; width: 20; height: 20; source: "qrc:/example/res/image/ic_home_github.png" } + onClicked:{ + showSuccess("点击IconButton") + } + } + FluIconButton{ + iconSource:FluentIcons.ChromeCloseContrast + disabled:icon_button_switch.checked + iconSize: 15 + text:"IconOnly" + display: Button.IconOnly + onClicked:{ + showSuccess("Button.IconOnly") + } + } + FluIconButton{ + iconSource:FluentIcons.ChromeCloseContrast + disabled:icon_button_switch.checked + iconSize: 15 + text:"TextOnly" + display: Button.TextOnly + onClicked:{ + showSuccess("Button.TextOnly") + } + } + FluIconButton{ + iconSource:FluentIcons.ChromeCloseContrast + disabled:icon_button_switch.checked + iconSize: 15 + text:"TextBesideIcon" + display: Button.TextBesideIcon + onClicked:{ + showSuccess("Button.TextBesideIcon") + } + } + FluIconButton{ + iconSource:FluentIcons.ChromeCloseContrast + disabled:icon_button_switch.checked + iconSize: 15 + text:"TextUnderIcon" + display: Button.TextUnderIcon + onClicked:{ + showSuccess("Button.TextUnderIcon") + } + } + } + FluToggleSwitch{ + id:icon_button_switch + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluIconButton{ + iconSource:FluentIcons.ChromeCloseContrast + onClicked: { + + } +}' + } + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + FluDropDownButton{ + disabled:drop_down_button_switch.checked + text:"DropDownButton" + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluMenuItem{ + text:"Menu_1" + } + FluMenuItem{ + text:"Menu_2" + } + FluMenuItem{ + text:"Menu_3" + } + FluMenuItem{ + text:"Menu_4" + onClicked: { + + } + } + } + FluToggleSwitch{ + id:drop_down_button_switch + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluDropDownButton{ + text:"DropDownButton" + FluMenuItem{ + text:"Menu_1" + }, + FluMenuItem{ + text:"Menu_2" + }, + FluMenuItem{ + text:"Menu_3" + }, + FluMenuItem{ + text:"Menu_4" + } +}' + } + + FluArea{ + Layout.fillWidth: true + height: 100 + paddings: 10 + Layout.topMargin: 20 + FluRadioButtons{ + spacing: 8 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluRadioButton{ + disabled:radio_button_switch.checked + text:"Radio Button_1" + } + FluRadioButton{ + disabled:radio_button_switch.checked + text:"Radio Button_2" + } + FluRadioButton{ + disabled:radio_button_switch.checked + text:"Radio Button_3" + } + } + FluToggleSwitch{ + id:radio_button_switch + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluRadioButton{ + checked:true + text:"Text Button" + onClicked: { + + } +}' + } + +} diff --git a/example/qml-Qt6/page/T_CalendarPicker.qml b/example/qml-Qt6/page/T_CalendarPicker.qml new file mode 100644 index 00000000..4cb67901 --- /dev/null +++ b/example/qml-Qt6/page/T_CalendarPicker.qml @@ -0,0 +1,50 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"CalendarPicker" + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 350 + paddings: 10 + FluCalendarView{ + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluCalendarView{ + +}' + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 80 + paddings: 10 + ColumnLayout{ + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluCalendarPicker{ + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluCalendarPicker{ + +}' + } + +} diff --git a/example/qml-Qt6/page/T_Captcha.qml b/example/qml-Qt6/page/T_Captcha.qml new file mode 100644 index 00000000..8ac59e75 --- /dev/null +++ b/example/qml-Qt6/page/T_Captcha.qml @@ -0,0 +1,54 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Captcha" + + + FluCaptcha{ + id:captcha + Layout.topMargin: 20 + MouseArea{ + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + captcha.refresh() + } + } + } + + FluButton{ + text:"Refresh" + Layout.topMargin: 20 + onClicked: { + captcha.refresh() + } + } + + RowLayout{ + spacing: 10 + Layout.topMargin: 10 + FluTextBox{ + id:text_box + placeholderText: "请输入验证码" + } + FluButton{ + text:"verify" + onClicked: { + var success = captcha.verify(text_box.text) + if(success){ + showSuccess("验证码正确") + }else{ + showError("错误验证,请重新输入") + } + } + } + } + + +} diff --git a/example/qml-Qt6/page/T_Carousel.qml b/example/qml-Qt6/page/T_Carousel.qml new file mode 100644 index 00000000..75cc647c --- /dev/null +++ b/example/qml-Qt6/page/T_Carousel.qml @@ -0,0 +1,132 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Carousel" + + ListModel{ + id:data_model + ListElement{ + url:"qrc:/example/res/image/banner_1.jpg" + } + ListElement{ + url:"qrc:/example/res/image/banner_2.jpg" + } + ListElement{ + url:"qrc:/example/res/image/banner_3.jpg" + } + } + + FluArea{ + Layout.fillWidth: true + height: 370 + paddings: 10 + Layout.topMargin: 20 + Column{ + spacing: 15 + anchors{ + verticalCenter: parent.verticalCenter + left:parent.left + } + FluText{ + text:"轮播图,支持无限轮播,无限滑动,用ListView实现的组件" + } + FluCarousel{ + radius:[5,5,5,5] + delegate: Component{ + Image { + anchors.fill: parent + source: model.url + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + } + Layout.topMargin: 20 + Layout.leftMargin: 5 + Component.onCompleted: { + model = [{url:"qrc:/example/res/image/banner_1.jpg"},{url:"qrc:/example/res/image/banner_2.jpg"},{url:"qrc:/example/res/image/banner_3.jpg"}] + } + } + } + } + + FluArea{ + Layout.fillWidth: true + height: 340 + paddings: 10 + Layout.topMargin: 10 + Column{ + spacing: 15 + anchors{ + verticalCenter: parent.verticalCenter + left:parent.left + } + FluCarousel{ + radius:[15,15,15,15] + loopTime:1500 + indicatorGravity: Qt.AlignHCenter | Qt.AlignTop + indicatorMarginTop:15 + delegate: Component{ + Item{ + anchors.fill: parent + Image { + anchors.fill: parent + source: model.url + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + Rectangle{ + height: 40 + width: parent.width + anchors.bottom: parent.bottom + color: "#33000000" + FluText{ + anchors.fill: parent + verticalAlignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignHCenter + text:model.title + color: FluColors.Grey10 + font.pixelSize: 15 + } + } + } + } + Layout.topMargin: 20 + Layout.leftMargin: 5 + Component.onCompleted: { + var arr = [] + arr.push({url:"qrc:/example/res/image/banner_1.jpg",title:"共同应对全球性问题"}) + arr.push({url:"qrc:/example/res/image/banner_2.jpg",title:"三小只全程没互动"}) + arr.push({url:"qrc:/example/res/image/banner_3.jpg",title:"有效投资扩大 激发增长动能"}) + model = arr + } + } + } + } + + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluCarousel{ + id:carousel + width: 400 + height: 300 + delegate: Component{ + Image { + anchors.fill: parent + source: model.url + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + } + Component.onCompleted: { + carousel.model = [{url:"qrc:/example/res/image/banner_1.jpg"},{url:"qrc:/example/res/image/banner_2.jpg"},{url:"qrc:/example/res/image/banner_3.jpg"}] + } +}' + } +} diff --git a/example/qml-Qt6/page/T_Chart.qml b/example/qml-Qt6/page/T_Chart.qml new file mode 100644 index 00000000..7582b210 --- /dev/null +++ b/example/qml-Qt6/page/T_Chart.qml @@ -0,0 +1,331 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Chart" + + function randomScalingFactor() { + return Math.random().toFixed(1); + } + + FluArea{ + height: 370 + width: 500 + paddings: 10 + Layout.topMargin: 20 + FluChart{ + anchors.fill: parent + chartType: 'scatter' + chartData: { + return { + datasets: [{ + label: 'My First dataset', + xAxisID: 'x-axis-1', + yAxisID: 'y-axis-1', + borderColor: '#ff5555', + backgroundColor: 'rgba(255,192,192,0.3)', + data: [{ + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }] + }, { + label: 'My Second dataset', + xAxisID: 'x-axis-1', + yAxisID: 'y-axis-2', + borderColor: '#5555ff', + backgroundColor: 'rgba(192,192,255,0.3)', + data: [{ + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }, { + x: randomScalingFactor(), + y: randomScalingFactor(), + }] + }] + }} + chartOptions: {return { + maintainAspectRatio: false, + responsive: true, + hoverMode: 'nearest', + intersect: true, + title: { + display: true, + text: 'Chart.js Scatter Chart - Multi Axis' + }, + scales: { + xAxes: [{ + position: 'bottom', + gridLines: { + zeroLineColor: 'rgba(0,0,0,1)' + } + }], + yAxes: [{ + type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance + display: true, + position: 'left', + id: 'y-axis-1', + }, { + type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance + display: true, + position: 'right', + reverse: true, + id: 'y-axis-2', + + // grid line settings + gridLines: { + drawOnChartArea: false, // only want the grid lines for one axis to show up + }, + }], + } + } + } + } + } + + FluArea{ + width: 500 + height: 370 + paddings: 10 + Layout.topMargin: 20 + FluChart{ + anchors.fill: parent + chartType: 'bar' + chartData: { return { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + datasets: [{ + label: 'Dataset 1', + backgroundColor: '#ff9999', + stack: 'Stack 0', + data: [ + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor() + ] + }, { + label: 'Dataset 2', + backgroundColor: '#9999ff', + stack: 'Stack 0', + data: [ + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor() + ] + }, { + label: 'Dataset 3', + backgroundColor: '#99ff99', + stack: 'Stack 1', + data: [ + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor() + ] + }] + } + } + + chartOptions: { return { + maintainAspectRatio: false, + title: { + display: true, + text: 'Chart.js Bar Chart - Stacked' + }, + tooltips: { + mode: 'index', + intersect: false + }, + responsive: true, + scales: { + xAxes: [{ + stacked: true, + }], + yAxes: [{ + stacked: true + }] + } + } + } + } + } + + FluArea{ + width: 500 + height: 370 + paddings: 10 + Layout.topMargin: 20 + FluChart{ + anchors.fill: parent + chartType: 'pie' + chartData: {return { + datasets: [{ + data: [ + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + ], + backgroundColor: [ + '#ffbbbb', + '#ffddaa', + '#ffffbb', + '#bbffbb', + '#bbbbff' + ], + label: 'Dataset 1' + }], + labels: [ + 'Red', + 'Orange', + 'Yellow', + 'Green', + 'Blue' + ] + }} + chartOptions: {return {maintainAspectRatio: false, responsive: true}} + } + } + + FluArea{ + width: 500 + height: 370 + paddings: 10 + Layout.topMargin: 20 + FluChart{ + anchors.fill: parent + chartType: 'line' + chartData: { return { + labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], + datasets: [{ + label: 'Filled', + fill: true, + backgroundColor: 'rgba(192,222,255,0.3)', + borderColor: 'rgba(128,192,255,255)', + data: [ + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor() + ], + }, { + label: 'Dashed', + fill: false, + backgroundColor: 'rgba(0,0,0,0)', + borderColor: '#009900', + borderDash: [5, 5], + data: [ + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor() + ], + }, { + label: 'Filled', + backgroundColor: 'rgba(0,0,0,0)', + borderColor: '#990000', + data: [ + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor() + ], + fill: false, + }] + } + } + + chartOptions: {return { + maintainAspectRatio: false, + responsive: true, + title: { + display: true, + text: 'Chart.js Line Chart' + }, + tooltips: { + mode: 'index', + intersect: false, + }, + hover: { + mode: 'nearest', + intersect: true + }, + scales: { + xAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Month' + } + }], + yAxes: [{ + display: true, + scaleLabel: { + display: true, + labelString: 'Value' + } + }] + } + } + } + } + } + +} diff --git a/example/qml-Qt6/page/T_CheckBox.qml b/example/qml-Qt6/page/T_CheckBox.qml new file mode 100644 index 00000000..e3c6ea4a --- /dev/null +++ b/example/qml-Qt6/page/T_CheckBox.qml @@ -0,0 +1,50 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"CheckBox" + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + Row{ + spacing: 30 + anchors.verticalCenter: parent.verticalCenter + FluCheckBox{ + disabled: check_box_switch.checked + } + FluCheckBox{ + disabled: check_box_switch.checked + text:"Right" + } + FluCheckBox{ + disabled: check_box_switch.checked + text:"Left" + textRight: false + } + } + FluToggleSwitch{ + id:check_box_switch + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluCheckBox{ + text:"Text" +}' + } + +} diff --git a/example/qml-Qt6/page/T_ColorPicker.qml b/example/qml-Qt6/page/T_ColorPicker.qml new file mode 100644 index 00000000..24954589 --- /dev/null +++ b/example/qml-Qt6/page/T_ColorPicker.qml @@ -0,0 +1,69 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"ColorPicker" + + FluArea{ + Layout.fillWidth: true + height: 280 + Layout.topMargin: 20 + paddings: 10 + ColumnLayout{ + anchors{ + verticalCenter: parent.verticalCenter + left:parent.left + } + FluText{ + text:"此颜色组件是Github上的开源项目" + } + FluTextButton{ + text:"https://github.com/rshest/qml-colorpicker" + onClicked: { + Qt.openUrlExternally(text) + } + } + FluColorView{ + + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluColorView{ + +}' + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 60 + paddings: 10 + + RowLayout{ + FluText{ + text:"点击选择颜色->" + Layout.alignment: Qt.AlignVCenter + } + FluColorPicker{ + + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluColorPicker{ + +}' + } + +} + diff --git a/example/qml-Qt6/page/T_ComboBox.qml b/example/qml-Qt6/page/T_ComboBox.qml new file mode 100644 index 00000000..15fba70b --- /dev/null +++ b/example/qml-Qt6/page/T_ComboBox.qml @@ -0,0 +1,81 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"ComboBox" + + FluArea{ + Layout.fillWidth: true + height: 80 + paddings: 5 + Layout.topMargin: 20 + Column{ + spacing: 5 + anchors.verticalCenter: parent.verticalCenter + FluText{ + text: "editable=false" + x:10 + } + FluComboBox { + model: ListModel { + id: model_1 + ListElement { text: "Banana" } + ListElement { text: "Apple" } + ListElement { text: "Coconut" } + } + } + } + } + + FluArea{ + Layout.fillWidth: true + height: 80 + paddings: 10 + Layout.topMargin: 20 + Column{ + spacing: 5 + anchors.verticalCenter: parent.verticalCenter + FluText{ + text: "editable=true" + x:5 + } + FluComboBox { + editable: true + font:FluTextStyle.BodyStrong + model: ListModel { + id: model_2 + ListElement { text: "Banana" } + ListElement { text: "Apple" } + ListElement { text: "Coconut" } + } + onAccepted: { + if (find(editText) === -1) + model_2.append({text: editText}) + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluComboBox{ + editable: true + model: ListModel { + id: model + ListElement { text: "Banana" } + ListElement { text: "Apple" } + ListElement { text: "Coconut" } + } + onAccepted: { + if (find(editText) === -1) + model.append({text: editText}) + } +}' + } + +} diff --git a/example/qml-Qt6/page/T_DatePicker.qml b/example/qml-Qt6/page/T_DatePicker.qml new file mode 100644 index 00000000..0fe6c4df --- /dev/null +++ b/example/qml-Qt6/page/T_DatePicker.qml @@ -0,0 +1,70 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"TimePicker" + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 80 + paddings: 10 + ColumnLayout{ + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluText{ + text:"showYear=true" + } + FluDatePicker{ + onCurrentChanged: { + showSuccess(current.toLocaleDateString()) + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluDatePicker{ + +}' + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 80 + paddings: 10 + ColumnLayout{ + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluText{ + text:"showYear=false" + } + + FluDatePicker{ + showYear:false + onCurrentChanged: { + showSuccess(current.toLocaleDateString()) + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluDatePicker{ + showYear:false +}' + } + +} diff --git a/example/qml-Qt6/page/T_Dialog.qml b/example/qml-Qt6/page/T_Dialog.qml new file mode 100644 index 00000000..b1e8b6b5 --- /dev/null +++ b/example/qml-Qt6/page/T_Dialog.qml @@ -0,0 +1,118 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Dialog" + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + FluButton{ + anchors.verticalCenter: parent.verticalCenter + Layout.topMargin: 20 + text:"Show Double Button Dialog" + onClicked: { + double_btn_dialog.open() + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluContentDialog{ + id:dialog + title:"友情提示" + message:"确定要退出程序么?" + negativeText:"取消" + buttonFlags: FluContentDialogType.NegativeButton | FluContentDialogType.PositiveButton + onNegativeClicked:{ + showSuccess("点击取消按钮") + } + positiveText:"确定" + onPositiveClicked:{ + showSuccess("点击确定按钮") + } + } + dialog.open()' + } + + FluContentDialog{ + id:double_btn_dialog + title:"友情提示" + message:"确定要退出程序么?" + buttonFlags: FluContentDialogType.NegativeButton | FluContentDialogType.PositiveButton + negativeText:"取消" + onNegativeClicked:{ + showSuccess("点击取消按钮") + } + positiveText:"确定" + onPositiveClicked:{ + showSuccess("点击确定按钮") + } + } + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + FluButton{ + anchors.verticalCenter: parent.verticalCenter + Layout.topMargin: 20 + text:"Show Triple Button Dialog" + onClicked: { + triple_btn_dialog.open() + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluContentDialog{ + id:dialog + title:"友情提示" + message:"确定要退出程序么?" + negativeText:"取消" + buttonFlags: FluContentDialogType.NeutralButton | FluContentDialogType.NegativeButton | FluContentDialogType.PositiveButton + negativeText:"取消" + onNegativeClicked:{ + showSuccess("点击取消按钮") + } + positiveText:"确定" + onPositiveClicked:{ + showSuccess("点击确定按钮") + } + neutralText:"最小化" + onNeutralClicked:{ + showSuccess("点击最小化按钮") + } + } + dialog.open()' + } + + FluContentDialog{ + id:triple_btn_dialog + title:"友情提示" + message:"确定要退出程序么?" + buttonFlags: FluContentDialogType.NeutralButton | FluContentDialogType.NegativeButton | FluContentDialogType.PositiveButton + negativeText:"取消" + onNegativeClicked:{ + showSuccess("点击取消按钮") + } + positiveText:"确定" + onPositiveClicked:{ + showSuccess("点击确定按钮") + } + neutralText:"最小化" + onNeutralClicked:{ + showSuccess("点击最小化按钮") + } + } +} diff --git a/example/qml-Qt6/page/T_Expander.qml b/example/qml-Qt6/page/T_Expander.qml new file mode 100644 index 00000000..36f390e3 --- /dev/null +++ b/example/qml-Qt6/page/T_Expander.qml @@ -0,0 +1,102 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Expander" + + FluArea{ + Layout.fillWidth: true + height: layout_column.height+20 + paddings: 10 + Layout.topMargin: 20 + Column{ + id:layout_column + spacing: 15 + anchors{ + top:parent.top + left:parent.left + } + + FluExpander{ + headerText:"打开一个单选框" + Layout.topMargin: 20 + Item{ + anchors.fill: parent + FluRadioButtons{ + spacing: 8 + anchors{ + top: parent.top + left: parent.left + topMargin: 15 + leftMargin: 15 + } + FluRadioButton{ + text:"Radio Button_1" + } + FluRadioButton{ + text:"Radio Button_2" + } + FluRadioButton{ + text:"Radio Button_3" + } + } + } + } + + FluExpander{ + Layout.topMargin: 20 + headerText:"打开一个滑动文本框" + Item{ + anchors.fill: parent + Flickable{ + id:scrollview + width: parent.width + height: parent.height + contentWidth: width + contentHeight: text_info.height + ScrollBar.vertical: FluScrollBar {} + FluText{ + id:text_info + width: scrollview.width + wrapMode: Text.WrapAnywhere + padding: 14 + text:"先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。宫中府中,俱为一体;陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨补阙漏,有所广益。将军向宠,性行淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐托付不效,以伤先帝之明;故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏。臣不胜受恩感激。今当远离,临表涕零,不知所言。" + } + } + } + } + } + } + + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluExpander{ + headerText:"打开一个单选框" + Item{ + anchors.fill: parent + Flickable{ + width: parent.width + height: parent.height + contentWidth: width + contentHeight: text_info.height + ScrollBar.vertical: FluScrollBar {} + FluText{ + id:text_info + width: scrollview.width + wrapMode: Text.WrapAnywhere + padding: 14 + text:"先帝创业未半而中道崩殂,今天下三分......"" + } + } + } +}' + } + + +} diff --git a/example/qml-Qt6/page/T_FlipView.qml b/example/qml-Qt6/page/T_FlipView.qml new file mode 100644 index 00000000..3baf06c1 --- /dev/null +++ b/example/qml-Qt6/page/T_FlipView.qml @@ -0,0 +1,117 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"FlipView" + + FluArea{ + Layout.fillWidth: true + height: 340 + paddings: 10 + Layout.topMargin: 20 + ColumnLayout{ + anchors.verticalCenter: parent.verticalCenter + FluText{ + text:"水平方向的FlipView" + } + FluFlipView{ + Image{ + source: "qrc:/example/res/image/banner_1.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + Image{ + source: "qrc:/example/res/image/banner_2.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + Image{ + source: "qrc:/example/res/image/banner_3.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluFlipView{ + Image{ + source: "qrc:/example/res/image/banner_1.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + Image{ + source: "qrc:/example/res/image/banner_1.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + Image{ + source: "qrc:/example/res/image/banner_1.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } +} +' + } + + FluArea{ + Layout.fillWidth: true + height: 340 + paddings: 10 + Layout.topMargin: 20 + ColumnLayout{ + anchors.verticalCenter: parent.verticalCenter + FluText{ + text:"垂直方向的FlipView" + } + FluFlipView{ + vertical:true + Image{ + source: "qrc:/example/res/image/banner_1.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + Image{ + source: "qrc:/example/res/image/banner_2.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + Image{ + source: "qrc:/example/res/image/banner_3.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluFlipView{ + vertical:true + Image{ + source: "qrc:/example/res/image/banner_1.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + Image{ + source: "qrc:/example/res/image/banner_1.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } + Image{ + source: "qrc:/example/res/image/banner_1.jpg" + asynchronous: true + fillMode:Image.PreserveAspectCrop + } +} +' + } +} diff --git a/example/qml-Qt6/page/T_Home.qml b/example/qml-Qt6/page/T_Home.qml new file mode 100644 index 00000000..c6acc672 --- /dev/null +++ b/example/qml-Qt6/page/T_Home.qml @@ -0,0 +1,284 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import "qrc:///example/qml/global" +import FluentUI + +FluScrollablePage{ + + launchMode: FluPageType.SingleTask + animDisabled: true + + ListModel{ + id:model_header + ListElement{ + icon:"qrc:/example/res/image/ic_home_github.png" + title:"FluentUI GitHub" + desc:"The latest FluentUI controls and styles for your applications." + url:"https://github.com/zhuzichu520/FluentUI" + } + } + + Item{ + Layout.fillWidth: true + Layout.preferredHeight: 320 + Image { + id: bg + fillMode:Image.PreserveAspectCrop + anchors.fill: parent + verticalAlignment: Qt.AlignTop + source: "qrc:/example/res/image/bg_home_header.png" + } + Rectangle{ + anchors.fill: parent + gradient: Gradient{ + GradientStop { position: 0.8; color: FluTheme.dark ? Qt.rgba(0,0,0,0) : Qt.rgba(1,1,1,0) } + GradientStop { position: 1.0; color: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1) } + } + } + FluText{ + text:"FluentUI Gallery" + font: FluTextStyle.TitleLarge + anchors{ + top: parent.top + left: parent.left + topMargin: 20 + leftMargin: 20 + } + } + + ListView{ + id: list + anchors{ + left: parent.left + right: parent.right + bottom: parent.bottom + } + orientation: ListView.Horizontal + height: 240 + model: model_header + header: Item{height: 10;width: 10} + footer: Item{height: 10;width: 10} + ScrollBar.horizontal: FluScrollBar{ + id: scrollbar_header + } + clip: false + delegate:Item{ + id: control + width: 220 + height: 240 + FluShadow{ + radius:8 + anchors.fill: item_content + } + FluItem{ + id:item_content + radius: [8,8,8,8] + width: 200 + height: 220 + anchors.centerIn: parent + FluAcrylic{ + anchors.fill: parent + tintColor: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1) + target: bg + tintOpacity: FluTheme.dark ? 0.8 : 0.9 + blurRadius : 40 + targetRect: Qt.rect(list.x-list.contentX+10+(control.width)*index,list.y+10,width,height) + } + Rectangle{ + anchors.fill: parent + radius: 8 + color:{ + if(FluTheme.dark){ + if(item_mouse.containsMouse){ + return Qt.rgba(1,1,1,0.03) + } + return Qt.rgba(0,0,0,0.0) + }else{ + if(item_mouse.containsMouse){ + return Qt.rgba(0,0,0,0.03) + } + return Qt.rgba(0,0,0,0.0) + } + } + } + ColumnLayout{ + Image { + Layout.topMargin: 20 + Layout.leftMargin: 20 + Layout.preferredWidth: 50 + Layout.preferredHeight: 50 + source: model.icon + } + FluText{ + text: model.title + font: FluTextStyle.Body + Layout.topMargin: 20 + Layout.leftMargin: 20 + } + FluText{ + text: model.desc + Layout.topMargin: 5 + Layout.preferredWidth: 160 + Layout.leftMargin: 20 + color: FluColors.Grey120 + font.pixelSize: 12 + wrapMode: Text.WrapAnywhere + } + } + FluIcon{ + iconSource: FluentIcons.OpenInNewWindow + iconSize: 15 + anchors{ + bottom: parent.bottom + right: parent.right + rightMargin: 10 + bottomMargin: 10 + } + } + MouseArea{ + id:item_mouse + anchors.fill: parent + hoverEnabled: true + onWheel: + (wheel)=>{ + if (wheel.angleDelta.y > 0) scrollbar_header.decrease() + else scrollbar_header.increase() + } + onClicked: { + Qt.openUrlExternally(model.url) + } + } + } + } + } + } + + Component{ + id:com_item + Item{ + width: 320 + height: 120 + FluArea{ + radius: 8 + width: 300 + height: 100 + anchors.centerIn: parent + Rectangle{ + anchors.fill: parent + radius: 8 + color:{ + if(FluTheme.dark){ + if(item_mouse.containsMouse){ + return Qt.rgba(1,1,1,0.03) + } + return Qt.rgba(0,0,0,0) + }else{ + if(item_mouse.containsMouse){ + return Qt.rgba(0,0,0,0.03) + } + return Qt.rgba(0,0,0,0) + } + } + } + Image{ + id:item_icon + height: 40 + width: 40 + source: modelData.image + anchors{ + left: parent.left + leftMargin: 20 + verticalCenter: parent.verticalCenter + } + } + + FluText{ + id:item_title + text:modelData.title + font: FluTextStyle.BodyStrong + anchors{ + left: item_icon.right + leftMargin: 20 + top: item_icon.top + } + } + + FluText{ + id:item_desc + text:modelData.desc + color:FluColors.Grey120 + wrapMode: Text.WrapAnywhere + elide: Text.ElideRight + font: FluTextStyle.Caption + maximumLineCount: 2 + anchors{ + left: item_title.left + right: parent.right + rightMargin: 20 + top: item_title.bottom + topMargin: 5 + } + } + + Rectangle{ + height: 12 + width: 12 + radius: 6 + color: FluTheme.primaryColor.dark + anchors{ + right: parent.right + top: parent.top + rightMargin: 14 + topMargin: 14 + } + } + + MouseArea{ + id:item_mouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + ItemsOriginal.startPageByItem(modelData) + } + } + } + } + } + + FluText{ + text: "Recently added samples" + font: FluTextStyle.Title + Layout.topMargin: 20 + Layout.leftMargin: 20 + } + + GridView{ + Layout.fillWidth: true + Layout.preferredHeight: contentHeight + cellHeight: 120 + cellWidth: 320 + model:ItemsOriginal.getRecentlyAddedData() + interactive: false + delegate: com_item + } + + FluText{ + text: "Recently updated samples" + font: FluTextStyle.Title + Layout.topMargin: 20 + Layout.leftMargin: 20 + } + + GridView{ + Layout.fillWidth: true + Layout.preferredHeight: contentHeight + cellHeight: 120 + cellWidth: 320 + interactive: false + model: ItemsOriginal.getRecentlyUpdatedData() + delegate: com_item + } + +} diff --git a/example/qml-Qt6/page/T_Http.qml b/example/qml-Qt6/page/T_Http.qml new file mode 100644 index 00000000..58942ade --- /dev/null +++ b/example/qml-Qt6/page/T_Http.qml @@ -0,0 +1,279 @@ +import QtQuick +import Qt.labs.platform +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Dialogs +import FluentUI +import "qrc:///example/qml/component" + +FluContentPage{ + + title:"Http" + + FluHttp{ + id:http + } + + Flickable{ + id:layout_flick + width: 160 + clip: true + anchors{ + top: parent.top + topMargin: 20 + bottom: parent.bottom + left: parent.left + } + ScrollBar.vertical: FluScrollBar {} + contentHeight:layout_column.height + Column{ + spacing: 2 + id:layout_column + width: parent.width + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Get请求" + onClicked: { + var callable = {} + callable.onStart = function(){ + showLoading() + } + callable.onFinish = function(){ + hideLoading() + } + callable.onSuccess = function(result){ + text_info.text = result + console.debug(result) + } + callable.onError = function(status,errorString){ + console.debug(status+";"+errorString) + } + http.get("https://httpbingo.org/get",callable) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Post表单请求" + onClicked: { + var callable = {} + callable.onStart = function(){ + showLoading() + } + callable.onFinish = function(){ + hideLoading() + } + callable.onSuccess = function(result){ + text_info.text = result + console.debug(result) + } + callable.onError = function(status,errorString){ + console.debug(status+";"+errorString) + } + var param = {} + param.custname = "朱子楚" + param.custtel = "1234567890" + param.custemail = "zhuzichu520@gmail.com" + http.post("https://httpbingo.org/post",callable,param) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Post Json请求" + onClicked: { + var callable = {} + callable.onStart = function(){ + showLoading() + } + callable.onFinish = function(){ + hideLoading() + } + callable.onSuccess = function(result){ + text_info.text = result + console.debug(result) + } + callable.onError = function(status,errorString){ + console.debug(status+";"+errorString) + } + var param = {} + param.custname = "朱子楚" + param.custtel = "1234567890" + param.custemail = "zhuzichu520@gmail.com" + http.postJson("https://httpbingo.org/post",callable,param) + } + } + FluButton{ + implicitWidth: parent.width + implicitHeight: 36 + text: "Post String请求" + onClicked: { + var callable = {} + callable.onStart = function(){ + showLoading() + } + callable.onFinish = function(){ + hideLoading() + } + callable.onSuccess = function(result){ + text_info.text = result + console.debug(result) + } + callable.onError = function(status,errorString){ + console.debug(status+";"+errorString) + } + var param = "我命由我不由天" + http.postString("https://httpbingo.org/post",callable,param) + } + } + FluButton{ + id:btn_download + implicitWidth: parent.width + implicitHeight: 36 + text: "下载文件" + onClicked: { + folder_dialog.open() + } + } + FluButton{ + id:btn_upload + implicitWidth: parent.width + implicitHeight: 36 + text: "文件上传" + onClicked: { + file_dialog.open() + } + } + } + } + + FileDialog { + id: file_dialog + onAccepted: { + var param = {} + for(var i=0;iStandard模式窗口,每次都会创建新窗口" + } + FluButton{ + text:"点击创建窗口" + onClicked: { + FluApp.navigate("/standardWindow") + } + } + } + } + + FluArea{ + Layout.fillWidth: true + height: 86 + paddings: 10 + Layout.topMargin: 10 + Column{ + spacing: 15 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluText{ + text:"SingleTask模式窗口,如果窗口存在,这激活该窗口" + textFormat: Text.RichText + } + FluButton{ + text:"点击创建窗口" + onClicked: { + FluApp.navigate("/singleTaskWindow") + } + } + } + } + + FluArea{ + Layout.fillWidth: true + height: 86 + paddings: 10 + Layout.topMargin: 10 + Column{ + spacing: 15 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluText{ + text:"SingleInstance模式窗口,如果窗口存在,则销毁窗口,然后新建窗口" + } + FluButton{ + text:"点击创建窗口" + onClicked: { + FluApp.navigate("/singleInstanceWindow") + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluWindow{ + //launchMode: FluWindowType.Standard + //launchMode: FluWindowType.SingleTask + launchMode: FluWindowType.SingleInstance +} +' + } + + + FluArea{ + Layout.fillWidth: true + height: 100 + paddings: 10 + Layout.topMargin: 20 + Column{ + spacing: 15 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluText{ + text:"页面跳转,不携带任何参数" + } + FluButton{ + text:"点击跳转" + onClicked: { + FluApp.navigate("/about") + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluButton{ + text:"点击跳转" + onClicked: { + FluApp.navigate("/about") + } +} +' + } + + FluArea{ + Layout.fillWidth: true + height: 130 + paddings: 10 + Layout.topMargin: 20 + + Column{ + spacing: 15 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluText{ + text:"页面跳转,并携带参数用户名:zhuzichu" + } + FluButton{ + text:"点击跳转到登录" + onClicked: { + loginPageRegister.launch({username:"zhuzichu"}) + } + } + FluText{ + text:"登录窗口返回过来的密码->"+password + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'property var loginPageRegister: registerForWindowResult("/login") + +Connections{ + target: loginPageRegister + function onResult(data) + { + password = data.password + } +} + +FluButton{ + text:"点击跳转" + onClicked: { + loginPageRegister.launch({username:"zhuzichu"}) + } +} +' + } + +} diff --git a/example/qml-Qt6/page/T_Pagination.qml b/example/qml-Qt6/page/T_Pagination.qml new file mode 100644 index 00000000..fa072dc6 --- /dev/null +++ b/example/qml-Qt6/page/T_Pagination.qml @@ -0,0 +1,49 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import "qrc:///example/qml/component" +import FluentUI + +FluScrollablePage{ + + title:"Pagination" + + FluArea{ + Layout.fillWidth: true + height: 200 + paddings: 10 + Layout.topMargin: 20 + ColumnLayout{ + spacing: 20 + anchors.verticalCenter: parent.verticalCenter + FluPagination{ + pageCurrent: 1 + pageButtonCount: 5 + itemCount: 5000 + } + FluPagination{ + pageCurrent: 2 + itemCount: 5000 + pageButtonCount: 7 + } + FluPagination{ + pageCurrent: 3 + itemCount: 5000 + pageButtonCount: 9 + } + } + + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluPagination{ + pageCurrent: 1 + itemCount: 1000 + pageButtonCount: 9 +}' + } + + +} diff --git a/example/qml-Qt6/page/T_Pivot.qml b/example/qml-Qt6/page/T_Pivot.qml new file mode 100644 index 00000000..0fe52dfd --- /dev/null +++ b/example/qml-Qt6/page/T_Pivot.qml @@ -0,0 +1,79 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Pivot" + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 400 + paddings: 10 + + FluPivot{ + anchors.fill: parent + currentIndex: 2 + FluPivotItem{ + title:"All" + contentItem:FluText{ + text:"All emails go here." + } + } + FluPivotItem{ + title:"Unread" + contentItem:FluText{ + text:"Unread emails go here." + } + } + FluPivotItem{ + title:"Flagged" + contentItem:FluText{ + text:"Flagged emails go here." + } + } + FluPivotItem{ + title:"Urgent" + contentItem:FluText{ + text:"Urgent emails go here." + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluPivot{ + anchors.fill: parent + FluPivotItem:{ + text:"All" + contentItem: FluText{ + text:"All emails go here." + } + } + FluPivotItem:{ + text:"Unread" + contentItem: FluText{ + text:"Unread emails go here." + } + } + FluPivotItem:{ + text:"Flagged" + contentItem: FluText{ + text:"Flagged emails go here." + } + } + FluPivotItem:{ + text:"Urgent" + contentItem: FluText{ + text:"Urgent emails go here." + } + } +} +' + } +} diff --git a/example/qml-Qt6/page/T_Progress.qml b/example/qml-Qt6/page/T_Progress.qml new file mode 100644 index 00000000..cb4740b5 --- /dev/null +++ b/example/qml-Qt6/page/T_Progress.qml @@ -0,0 +1,103 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Progress" + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 130 + paddings: 10 + + ColumnLayout{ + spacing: 10 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluText{ + text: "indeterminate = true" + } + FluProgressBar{ + } + FluProgressRing{ + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluProgressBar{ + +} +FluProgressRing{ + +} +' + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 286 + paddings: 10 + + ColumnLayout{ + spacing: 10 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluText{ + text: "indeterminate = false" + } + FluProgressBar{ + indeterminate: false + value:slider.value/100 + Layout.topMargin: 10 + } + FluProgressBar{ + indeterminate: false + value:slider.value/100 + progressVisible: true + Layout.topMargin: 10 + } + FluProgressRing{ + indeterminate: false + value: slider.value/100 + Layout.topMargin: 10 + } + FluProgressRing{ + progressVisible: true + indeterminate: false + value: slider.value/100 + } + FluSlider{ + id:slider + Component.onCompleted: { + value = 50 + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluProgressBar{ + indeterminate: false +} +FluProgressRing{ + indeterminate: false + progressVisible: true +} +' + } + + +} diff --git a/example/qml-Qt6/page/T_QRCode.qml b/example/qml-Qt6/page/T_QRCode.qml new file mode 100644 index 00000000..24f60f20 --- /dev/null +++ b/example/qml-Qt6/page/T_QRCode.qml @@ -0,0 +1,106 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import Qt5Compat.GraphicalEffects +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"QRCode" + + FluQRCode{ + id:qrcode + Layout.topMargin: 20 + size:slider_size.value + text:text_box.text + color:color_picker.colorValue + bgColor: bgcolor_picker.colorValue + margins:slider_margins.value + Layout.preferredWidth: size + Layout.preferredHeight: size + } + + RowLayout{ + spacing: 10 + Layout.topMargin: 20 + FluText{ + text:"text:" + Layout.alignment: Qt.AlignVCenter + } + FluTextBox{ + id:text_box + text:"会磨刀的小猪" + } + } + + RowLayout{ + spacing: 10 + Layout.topMargin: 10 + FluText{ + text:"color:" + Layout.alignment: Qt.AlignVCenter + } + FluColorPicker{ + id:color_picker + Component.onCompleted: { + setColor(Qt.rgba(0,0,0,1)) + } + } + } + + RowLayout{ + spacing: 10 + Layout.topMargin: 10 + FluText{ + text:"bgColor:" + Layout.alignment: Qt.AlignVCenter + } + FluColorPicker{ + id:bgcolor_picker + Component.onCompleted: { + setColor(Qt.rgba(1,1,1,1)) + } + } + } + + RowLayout{ + spacing: 10 + FluText{ + text:"margins:" + Layout.alignment: Qt.AlignVCenter + } + FluSlider{ + id:slider_margins + from:0 + to:80 + value: 0 + } + } + + RowLayout{ + spacing: 10 + FluText{ + text:"size:" + Layout.alignment: Qt.AlignVCenter + } + FluSlider{ + id:slider_size + from:120 + to:260 + value: 120 + } + } + + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: 20 + code:'FluQRCode{ + color:"red" + text:"会磨刀的小猪" + size:100 +}' + } + +} diff --git a/example/qml-Qt6/page/T_RadioButton.qml b/example/qml-Qt6/page/T_RadioButton.qml new file mode 100644 index 00000000..07b383b7 --- /dev/null +++ b/example/qml-Qt6/page/T_RadioButton.qml @@ -0,0 +1,101 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"RadioButton" + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + Row{ + spacing: 30 + anchors.verticalCenter: parent.verticalCenter + FluRadioButton{ + disabled: radio_button_switch.checked + } + FluRadioButton{ + disabled: radio_button_switch.checked + text:"Right" + } + FluRadioButton{ + disabled: radio_button_switch.checked + text:"Left" + textRight: false + } + } + FluToggleSwitch{ + id:radio_button_switch + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluRadioButton{ + text:"Text" +}' + } + + FluArea{ + Layout.fillWidth: true + height: 100 + paddings: 10 + Layout.topMargin: 20 + FluRadioButtons{ + spacing: 8 + anchors.verticalCenter: parent.verticalCenter + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluRadioButton{ + disabled: radio_button_switch2.checked + text:"Radio Button_1" + } + FluRadioButton{ + disabled: radio_button_switch2.checked + text:"Radio Button_2" + } + FluRadioButton{ + disabled: radio_button_switch2.checked + text:"Radio Button_3" + } + } + FluToggleSwitch{ + id:radio_button_switch2 + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluRadioButtons{ + spacing: 8 + FluRadioButton{ + text:"Radio Button_1" + } + FluRadioButton{ + text:"Radio Button_2" + } + FluRadioButton{ + text:"Radio Button_3" + } +}' + } + +} diff --git a/example/qml-Qt6/page/T_RatingControl.qml b/example/qml-Qt6/page/T_RatingControl.qml new file mode 100644 index 00000000..4c39d380 --- /dev/null +++ b/example/qml-Qt6/page/T_RatingControl.qml @@ -0,0 +1,35 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage { + + title: "RatingControl" + + FluArea { + Layout.fillWidth: true + height: 100 + paddings: 10 + Layout.topMargin: 20 + + Column { + spacing: 10 + anchors.verticalCenter: parent.verticalCenter + FluRatingControl {} + FluRatingControl { + number: 10 + } + } + } + + CodeExpander { + Layout.fillWidth: true + Layout.topMargin: -1 + code: 'FluRatingControl{ + +}' + } +} diff --git a/example/qml-Qt6/page/T_Rectangle.qml b/example/qml-Qt6/page/T_Rectangle.qml new file mode 100644 index 00000000..9cfe42aa --- /dev/null +++ b/example/qml-Qt6/page/T_Rectangle.qml @@ -0,0 +1,147 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Rectangle" + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 460 + paddings: 10 + + Column{ + spacing: 15 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + + RowLayout{ + Layout.topMargin: 20 + FluRectangle{ + width: 50 + height: 50 + color:"#0078d4" + radius:[0,0,0,0] + } + FluRectangle{ + width: 50 + height: 50 + color:"#744da9" + radius:[15,15,15,15] + } + FluRectangle{ + width: 50 + height: 50 + color:"#ffeb3b" + radius:[15,0,0,0] + } + FluRectangle{ + width: 50 + height: 50 + color:"#f7630c" + radius:[0,15,0,0] + } + FluRectangle{ + width: 50 + height: 50 + color:"#e71123" + radius:[0,0,15,0] + } + FluRectangle{ + width: 50 + height: 50 + color:"#b4009e" + radius:[0,0,0,15] + } + } + FluText{ + text:"配合图片使用" + font: FluTextStyle.Subtitle + Layout.topMargin: 20 + } + RowLayout{ + spacing: 14 + FluRectangle{ + width: 50 + height: 50 + radius:[25,0,25,25] + Image { + asynchronous: true + anchors.fill: parent + source: "qrc:/example/res/svg/avatar_1.svg" + sourceSize: Qt.size(width,height) + } + } + FluRectangle{ + width: 50 + height: 50 + radius:[10,10,10,10] + Image { + asynchronous: true + anchors.fill: parent + sourceSize: Qt.size(width,height) + source: "qrc:/example/res/svg/avatar_2.svg" + } + } + FluRectangle{ + width: 50 + height: 50 + radius:[25,25,25,25] + Image { + asynchronous: true + anchors.fill: parent + sourceSize: Qt.size(width,height) + source: "qrc:/example/res/svg/avatar_3.svg" + } + } + FluRectangle{ + width: 50 + height: 50 + radius:[0,25,25,25] + Image { + asynchronous: true + anchors.fill: parent + sourceSize: Qt.size(width,height) + source: "qrc:/example/res/svg/avatar_4.svg" + } + } + } + FluRectangle{ + width: 1920/5 + height: 1200/5 + radius:[15,15,15,15] + Image { + asynchronous: true + source: "qrc:/example/res/image/banner_1.jpg" + anchors.fill: parent + sourceSize: Qt.size(2*width,2*height) + } + Layout.topMargin: 20 + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluRectangle{ + radius: [25,25,25,25] + width: 50 + height: 50 + Image{ + asynchronous: true + anchors.fill: parent + source: "qrc:/example/res/svg/avatar_4.svg" + sourceSize: Qt.size(width,height) + } +}' + } + + +} diff --git a/example/qml-Qt6/page/T_RemoteLoader.qml b/example/qml-Qt6/page/T_RemoteLoader.qml new file mode 100644 index 00000000..03dd73cf --- /dev/null +++ b/example/qml-Qt6/page/T_RemoteLoader.qml @@ -0,0 +1,14 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluPage{ + launchMode: FluPageType.SingleTop + FluRemoteLoader{ + anchors.fill: parent + source: "https://zhu-zichu.gitee.io/T_RemoteLoader.qml" + } +} diff --git a/example/qml-Qt6/page/T_Screenshot.qml b/example/qml-Qt6/page/T_Screenshot.qml new file mode 100644 index 00000000..1a5cf38f --- /dev/null +++ b/example/qml-Qt6/page/T_Screenshot.qml @@ -0,0 +1,55 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Screenshot" + + FluArea{ + Layout.fillWidth: true + height: 100 + paddings: 10 + Layout.topMargin: 20 + + FluFilledButton{ + anchors.verticalCenter: parent.verticalCenter + text:"Open Screenshot" + onClicked: { + screenshot.open() + } + } + } + + Rectangle{ + Layout.preferredHeight: 400 + Layout.preferredWidth: 400 + Layout.topMargin: 10 + Layout.leftMargin: 4 + Layout.bottomMargin: 4 + color: FluTheme.dark ? FluColors.Black : FluColors.White + FluShadow{ + radius: 0 + color: FluTheme.primaryColor.dark + } + Image{ + id:image + anchors.fill: parent + fillMode: Image.PreserveAspectFit + asynchronous: true + } + } + + FluScreenshot{ + id:screenshot + captrueMode: FluScreenshotType.File + saveFolder: FluTools.getApplicationDirPath()+"/screenshot" + onCaptrueCompleted: + (captrue)=>{ + image.source = captrue + } + } +} diff --git a/example/qml-Qt6/page/T_Settings.qml b/example/qml-Qt6/page/T_Settings.qml new file mode 100644 index 00000000..8c2fcefd --- /dev/null +++ b/example/qml-Qt6/page/T_Settings.qml @@ -0,0 +1,108 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/global" +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Settings" + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 128 + paddings: 10 + + ColumnLayout{ + spacing: 5 + anchors{ + top: parent.top + left: parent.left + } + FluText{ + text:lang.dark_mode + font: FluTextStyle.BodyStrong + Layout.bottomMargin: 4 + } + Repeater{ + model: [{title:"System",mode:FluThemeType.System},{title:"Light",mode:FluThemeType.Light},{title:"Dark",mode:FluThemeType.Dark}] + delegate: FluRadioButton{ + checked : FluTheme.darkMode === modelData.mode + text:modelData.title + clickListener:function(){ + FluTheme.darkMode = modelData.mode + } + } + } + } + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 160 + paddings: 10 + + ColumnLayout{ + spacing: 5 + anchors{ + top: parent.top + left: parent.left + } + FluText{ + text:lang.navigation_view_display_mode + font: FluTextStyle.BodyStrong + Layout.bottomMargin: 4 + } + Repeater{ + model: [{title:"Open",mode:FluNavigationViewType.Open},{title:"Compact",mode:FluNavigationViewType.Compact},{title:"Minimal",mode:FluNavigationViewType.Minimal},{title:"Auto",mode:FluNavigationViewType.Auto}] + delegate: FluRadioButton{ + checked : MainEvent.displayMode===modelData.mode + text:modelData.title + clickListener:function(){ + MainEvent.displayMode = modelData.mode + } + } + } + } + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 80 + paddings: 10 + + ColumnLayout{ + spacing: 10 + anchors{ + top: parent.top + left: parent.left + } + + FluText{ + text:lang.locale + font: FluTextStyle.BodyStrong + Layout.bottomMargin: 4 + } + + Flow{ + spacing: 5 + Repeater{ + model: ["Zh","En"] + delegate: FluRadioButton{ + checked: appInfo.lang.objectName === modelData + text:modelData + clickListener:function(){ + appInfo.changeLang(modelData) + } + } + } + } + } + } + +} diff --git a/example/qml-Qt6/page/T_Slider.qml b/example/qml-Qt6/page/T_Slider.qml new file mode 100644 index 00000000..517904a6 --- /dev/null +++ b/example/qml-Qt6/page/T_Slider.qml @@ -0,0 +1,50 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import "qrc:///example/qml/component" +import FluentUI + +FluScrollablePage{ + + title:"Slider" + + FluArea{ + Layout.fillWidth: true + height: 100 + paddings: 10 + Layout.topMargin: 20 + FluSlider{ + anchors.verticalCenter: parent.verticalCenter + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluSlider{ + value:50 +}' + } + + FluArea{ + Layout.fillWidth: true + height: 200 + paddings: 10 + Layout.topMargin: 20 + FluSlider{ + orientation: Qt.Vertical + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluSlider{ + orientation: Qt.Vertical + value:50 +}' + } + + +} diff --git a/example/qml-Qt6/page/T_StatusView.qml b/example/qml-Qt6/page/T_StatusView.qml new file mode 100644 index 00000000..3a94578d --- /dev/null +++ b/example/qml-Qt6/page/T_StatusView.qml @@ -0,0 +1,86 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"StatusView" + + FluArea{ + id:layout_actions + Layout.fillWidth: true + Layout.topMargin: 20 + height: 50 + paddings: 10 + RowLayout{ + spacing: 14 + FluDropDownButton{ + id:btn_status_mode + Layout.preferredWidth: 140 + text:"Loading" + FluMenuItem{ + text:"Loading" + onClicked: { + btn_status_mode.text = text + status_view.statusMode = FluStatusViewType.Loading + } + } + FluMenuItem{ + text:"Empty" + onClicked: { + btn_status_mode.text = text + status_view.statusMode = FluStatusViewType.Empty + } + } + FluMenuItem{ + text:"Error" + onClicked: { + btn_status_mode.text = text + status_view.statusMode = FluStatusViewType.Error + } + } + FluMenuItem{ + text:"Success" + onClicked: { + btn_status_mode.text = text + status_view.statusMode = FluStatusViewType.Success + } + } + } + } + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 10 + height: 380 + paddings: 10 + FluStatusView{ + id:status_view + anchors.fill: parent + onErrorClicked:{ + showError("点击重新加载") + } + Rectangle { + anchors.fill: parent + color:FluTheme.primaryColor.dark + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluStatusView{ + anchors.fill: parent + statusMode: FluStatusViewType.Loading + Rectangle{ + anchors.fill: parent + color:FluTheme.primaryColor.dark + } +}' + } + +} diff --git a/example/qml-Qt6/page/T_TabView.qml b/example/qml-Qt6/page/T_TabView.qml new file mode 100644 index 00000000..e50f4de1 --- /dev/null +++ b/example/qml-Qt6/page/T_TabView.qml @@ -0,0 +1,130 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + property var colors : [FluColors.Yellow,FluColors.Orange,FluColors.Red,FluColors.Magenta,FluColors.Purple,FluColors.Blue,FluColors.Teal,FluColors.Green] + + title:"TabView" + + Component{ + id:com_page + Rectangle{ + anchors.fill: parent + color: argument + } + } + + function newTab(){ + tab_view.appendTab("qrc:/example/res/image/favicon.ico","Document "+tab_view.count(),com_page,colors[Math.floor(Math.random() * 8)].dark) + } + + Component.onCompleted: { + newTab() + newTab() + newTab() + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 50 + paddings: 10 + RowLayout{ + spacing: 14 + FluDropDownButton{ + id:btn_tab_width_behavior + Layout.preferredWidth: 140 + text:"Equal" + FluMenuItem{ + text:"Equal" + onClicked: { + btn_tab_width_behavior.text = text + tab_view.tabWidthBehavior = FluTabViewType.Equal + } + } + FluMenuItem{ + text:"SizeToContent" + onClicked: { + btn_tab_width_behavior.text = text + tab_view.tabWidthBehavior = FluTabViewType.SizeToContent + } + } + FluMenuItem{ + text:"Compact" + onClicked: { + btn_tab_width_behavior.text = text + tab_view.tabWidthBehavior = FluTabViewType.Compact + } + } + } + FluDropDownButton{ + id:btn_close_button_visibility + text:"Always" + Layout.preferredWidth: 120 + FluMenuItem{ + text:"Nerver" + onClicked: { + btn_close_button_visibility.text = text + tab_view.closeButtonVisibility = FluTabViewType.Nerver + } + } + FluMenuItem{ + text:"Always" + onClicked: { + btn_close_button_visibility.text = text + tab_view.closeButtonVisibility = FluTabViewType.Always + } + } + FluMenuItem{ + text:"OnHover" + onClicked: { + btn_close_button_visibility.text = text + tab_view.closeButtonVisibility = FluTabViewType.OnHover + } + } + } + } + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 15 + height: 400 + paddings: 10 + FluTabView{ + id:tab_view + onNewPressed:{ + newTab() + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluTabView{ + anchors.fill: parent + Component.onCompleted: { + newTab() + newTab() + newTab() + } + Component{ + id:com_page + Rectangle{ + anchors.fill: parent + color: argument + } + } + function newTab(){ + tab_view.appendTab("qrc:/example/res/image/favicon.ico","Document 1",com_page,argument) + } +} +' + } + +} diff --git a/example/qml-Qt6/page/T_TableView.qml b/example/qml-Qt6/page/T_TableView.qml new file mode 100644 index 00000000..ffd6f174 --- /dev/null +++ b/example/qml-Qt6/page/T_TableView.qml @@ -0,0 +1,171 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluContentPage{ + + title:"TableView" + + Component.onCompleted: { + loadData(1,1000) + } + + Component{ + id:com_action + Item{ + RowLayout{ + anchors.centerIn: parent + FluButton{ + text:"删除" + onClicked: { + table_view.closeEditor() + tableModel.removeRow(row) + } + } + FluFilledButton{ + text:"编辑" + onClicked: { + showSuccess(JSON.stringify(tableModel.getRow(row))) + } + } + } + } + } + + function loadData(page,count){ + var numbers = [100, 300, 500, 1000]; + function getRandomAge() { + var randomIndex = Math.floor(Math.random() * numbers.length); + return numbers[randomIndex]; + } + var names = ["孙悟空", "猪八戒", "沙和尚", "唐僧","白骨夫人","金角大王","熊山君","黄风怪","银角大王"]; + function getRandomName(){ + var randomIndex = Math.floor(Math.random() * names.length); + return names[randomIndex]; + } + var nicknames = ["复海大圣","混天大圣","移山大圣","通风大圣","驱神大圣","齐天大圣","平天大圣"] + function getRandomNickname(){ + var randomIndex = Math.floor(Math.random() * nicknames.length); + return nicknames[randomIndex]; + } + var addresses = ["傲来国界花果山水帘洞","傲来国界坎源山脏水洞","大唐国界黑风山黑风洞","大唐国界黄风岭黄风洞","大唐国界骷髅山白骨洞","宝象国界碗子山波月洞","宝象国界平顶山莲花洞","宝象国界压龙山压龙洞","乌鸡国界号山枯松涧火云洞","乌鸡国界衡阳峪黑水河河神府"] + function getRandomAddresses(){ + var randomIndex = Math.floor(Math.random() * addresses.length); + return addresses[randomIndex]; + } + const dataSource = [] + for(var i=0;i element === Number(display)) + selectAll() + } + onCommit: { + display = editText + tableView.closeEditor() + } + } + } + + FluTableView{ + id:table_view + anchors{ + left: parent.left + right: parent.right + top: parent.top + bottom: gagination.top + } + anchors.topMargin: 20 + columnSource:[ + { + title: '姓名', + dataIndex: 'name', + readOnly:true, + }, + { + title: '年龄', + dataIndex: 'age', + editDelegate:com_combobox, + width:100, + minimumWidth:100, + maximumWidth:100 + }, + { + title: '住址', + dataIndex: 'address', + width:200, + minimumWidth:100, + maximumWidth:250 + }, + { + title: '别名', + dataIndex: 'nickname', + width:100, + minimumWidth:80, + maximumWidth:200 + }, + { + title: '长字符串', + dataIndex: 'longstring', + width:200, + minimumWidth:100, + maximumWidth:300 + }, + { + title: '操作', + dataIndex: 'action', + width:160, + minimumWidth:160, + maximumWidth:160 + } + ] + } + + FluPagination{ + id:gagination + anchors{ + bottom: parent.bottom + left: parent.left + } + pageCurrent: 1 + itemCount: 100000 + pageButtonCount: 7 + __itemPerPage: 1000 + onRequestPage: + (page,count)=> { + table_view.closeEditor() + loadData(page,count) + table_view.resetPosition() + } + } + + + +} diff --git a/example/qml-Qt6/page/T_Text.qml b/example/qml-Qt6/page/T_Text.qml new file mode 100644 index 00000000..c5624f62 --- /dev/null +++ b/example/qml-Qt6/page/T_Text.qml @@ -0,0 +1,32 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Text" + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 60 + paddings: 10 + + FluCopyableText{ + text: "这是一个可以支持复制的Text" + anchors.verticalCenter: parent.verticalCenter + } + + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluCopyableText{ + text:"这是一个可以支持复制的Text" +}' + } + +} diff --git a/example/qml-Qt6/page/T_TextBox.qml b/example/qml-Qt6/page/T_TextBox.qml new file mode 100644 index 00000000..768fb481 --- /dev/null +++ b/example/qml-Qt6/page/T_TextBox.qml @@ -0,0 +1,191 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + launchMode: FluPageType.SingleInstance + + title:"TextBox" + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + + FluTextBox{ + placeholderText: "单行输入框" + disabled:text_box_switch.checked + cleanEnabled: true + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + } + + FluToggleSwitch{ + id:text_box_switch + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluTextBox{ + placeholderText:"单行输入框" +}' + } + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + + FluPasswordBox{ + placeholderText: "请输入密码" + disabled:password_box_switch.checked + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + } + FluToggleSwitch{ + id:password_box_switch + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluPasswordBox{ + placeholderText:"请输入密码" +}' + } + + + FluArea{ + Layout.fillWidth: true + height: 36+multiine_textbox.height + paddings: 10 + Layout.topMargin: 20 + + FluMultilineTextBox{ + id:multiine_textbox + placeholderText: "多行输入框" + disabled:text_box_multi_switch.checked + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + } + + FluToggleSwitch{ + id:text_box_multi_switch + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluMultilineTextBox{ + placeholderText:"多行输入框" +}' + } + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + FluAutoSuggestBox{ + placeholderText: "AutoSuggestBox" + items:generateRandomNames(100) + disabled:text_box_suggest_switch.checked + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + } + FluToggleSwitch{ + id:text_box_suggest_switch + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluAutoSuggestBox{ + placeholderText:"AutoSuggestBox" +}' + } + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + FluSpinBox{ + disabled: spin_box_switch.checked + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + } + FluToggleSwitch{ + id:spin_box_switch + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluSpinBox{ + +}' + } + + function generateRandomNames(numNames) { + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const names = []; + function generateRandomName() { + const nameLength = Math.floor(Math.random() * 5) + 4; + let name = ''; + for (let i = 0; i < nameLength; i++) { + const letterIndex = Math.floor(Math.random() * 26); + name += alphabet.charAt(letterIndex); + } + return name; + } + for (let i = 0; i < numNames; i++) { + const name = generateRandomName(); + names.push({title:name}); + } + return names; + } + + +} diff --git a/example/qml-Qt6/page/T_Theme.qml b/example/qml-Qt6/page/T_Theme.qml new file mode 100644 index 00000000..a0b73877 --- /dev/null +++ b/example/qml-Qt6/page/T_Theme.qml @@ -0,0 +1,99 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Theme" + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 270 + paddings: 10 + ColumnLayout{ + spacing:0 + anchors{ + left: parent.left + } + RowLayout{ + Layout.topMargin: 10 + Repeater{ + model: [FluColors.Yellow,FluColors.Orange,FluColors.Red,FluColors.Magenta,FluColors.Purple,FluColors.Blue,FluColors.Teal,FluColors.Green] + delegate: FluRectangle{ + width: 42 + height: 42 + radius: [4,4,4,4] + color: mouse_item.containsMouse ? Qt.lighter(modelData.normal,1.1) : modelData.normal + FluIcon { + anchors.centerIn: parent + iconSource: FluentIcons.AcceptMedium + iconSize: 15 + visible: modelData === FluTheme.primaryColor + color: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1) + } + MouseArea{ + id:mouse_item + anchors.fill: parent + hoverEnabled: true + onClicked: { + FluTheme.primaryColor = modelData + } + } + } + } + } + FluText{ + text:"夜间模式" + Layout.topMargin: 20 + } + FluToggleSwitch{ + Layout.topMargin: 5 + checked: FluTheme.dark + onClicked: { + if(FluTheme.dark){ + FluTheme.darkMode = FluThemeType.Light + }else{ + FluTheme.darkMode = FluThemeType.Dark + } + } + } + FluText{ + text:"native文本渲染" + Layout.topMargin: 20 + } + FluToggleSwitch{ + Layout.topMargin: 5 + checked: FluTheme.nativeText + onClicked: { + FluTheme.nativeText = !FluTheme.nativeText + } + } + FluText{ + text:"开启动画效果" + Layout.topMargin: 20 + } + FluToggleSwitch{ + Layout.topMargin: 5 + checked: FluTheme.enableAnimation + onClicked: { + FluTheme.enableAnimation = !FluTheme.enableAnimation + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluTheme.primaryColor = FluColors.Orange + +FluTheme.dark = true + +FluTheme.nativeText = true' + } + + +} diff --git a/example/qml-Qt6/page/T_TimePicker.qml b/example/qml-Qt6/page/T_TimePicker.qml new file mode 100644 index 00000000..eefce75a --- /dev/null +++ b/example/qml-Qt6/page/T_TimePicker.qml @@ -0,0 +1,79 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"TimePicker" + launchMode: FluPageType.SingleInstance + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 80 + paddings: 10 + + ColumnLayout{ + + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + + FluText{ + text:"hourFormat=FluTimePickerType.H" + } + + FluTimePicker{ + onCurrentChanged: { + showSuccess(current.toLocaleTimeString(Qt.locale("de_DE"))) + } + } + + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluTimePicker{ + +}' + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 80 + paddings: 10 + + ColumnLayout{ + + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + + FluText{ + text:"hourFormat=FluTimePickerType.HH" + } + + FluTimePicker{ + hourFormat:FluTimePickerType.HH + onCurrentChanged: { + showSuccess(current.toLocaleTimeString(Qt.locale("de_DE"))) + } + } + + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluTimePicker{ + hourFormat:FluTimePickerType.HH +}' + } + +} diff --git a/example/qml-Qt6/page/T_Timeline.qml b/example/qml-Qt6/page/T_Timeline.qml new file mode 100644 index 00000000..3e7ae2b7 --- /dev/null +++ b/example/qml-Qt6/page/T_Timeline.qml @@ -0,0 +1,170 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Timeline" + + Component{ + id:com_dot + Rectangle{ + width: 16 + height: 16 + radius: 8 + border.width: 4 + border.color: FluTheme.dark ? FluColors.Teal.lighter : FluColors.Teal.dark + } + } + + Component{ + id:com_lable + FluText{ + wrapMode: Text.WrapAnywhere + font.bold: true + horizontalAlignment: isRight ? Qt.AlignRight : Qt.AlignLeft + text: modelData.lable + color: FluTheme.dark ? FluColors.Teal.lighter : FluColors.Teal.dark + MouseArea{ + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + showSuccess(modelData.lable) + } + } + } + } + + Component{ + id:com_text + FluText{ + wrapMode: Text.WrapAnywhere + horizontalAlignment: isRight ? Qt.AlignRight : Qt.AlignLeft + text: modelData.text + font.bold: true + linkColor: FluTheme.dark ? FluColors.Teal.lighter : FluColors.Teal.dark + onLinkActivated: + (link)=> { + Qt.openUrlExternally(link) + } + onLinkHovered: + (link)=> { + if(link === ""){ + FluTools.restoreOverrideCursor() + }else{ + FluTools.setOverrideCursor(Qt.PointingHandCursor) + } + } + } + } + + ListModel{ + id:list_model + ListElement{ + lable:"2013-09-01" + text:'  考上中国皮城大学,杰斯武器工坊专业' + } + ListElement{ + lable:"2017-07-01" + text:"大学毕业,在寝室打了4年LOL,没想到毕业还要找工作,瞬间蒙蔽,害" + } + ListElement{ + lable:"2017-09-01" + text:"开始找工作,毕业即失业!回农村老家躺平,继承三亩良田" + } + ListElement{ + lable:"2018-02-01" + text:"玩了一年没钱,惨,出去找工作上班" + } + ListElement{ + lable:"2018-03-01" + text:"找到一份Android外包开发岗位,开发了一个Android应用,满满成就感!前端、服务端、Flutter也都懂一丢丢,什么都会什么都不精通,钱途无望" + } + ListElement{ + lable:"2020-06-01" + text:"由于某个项目紧急,临时加入Qt项目组(就因为大学学了点C++),本来是想进去打个酱油,到后面竟然成开发主力,坑啊" + } + ListElement{ + lable:"2022-08-01" + text:"额,被老板卖到甲方公司,走时老板还问我想不想去,我说:'哪里工资高就去哪里',老板:'无语'" + } + ListElement{ + lable:"2023-02-28" + text:"开发FluentUI组件库" + } + ListElement{ + lable:"2023-03-28" + text:'将FluentUI源码开源到github,并发布视频到B站' + lableDelegate:()=>com_lable + textDelegate:()=>com_text + dot:()=>com_dot + } + } + + RowLayout{ + spacing: 20 + Layout.topMargin: 20 + FluTextBox{ + id:text_box + text:"Technical testing 2015-09-01" + } + FluFilledButton{ + text:"Append" + onClicked: { + list_model.append({text:text_box.text}) + } + } + FluFilledButton{ + text:"clear" + onClicked: { + list_model.clear() + } + } + } + + RowLayout{ + Layout.topMargin: 10 + FluText{ + text:"mode:" + } + FluDropDownButton{ + id:btn_mode + Layout.preferredWidth: 100 + text:"Alternate" + FluMenuItem{ + text:"Left" + onClicked: { + btn_mode.text = text + time_line.mode = FluTimelineType.Left + } + } + FluMenuItem{ + text:"Right" + onClicked: { + btn_mode.text = text + time_line.mode = FluTimelineType.Right + } + } + FluMenuItem{ + text:"Alternate" + onClicked: { + btn_mode.text = text + time_line.mode = FluTimelineType.Alternate + } + } + } + } + + FluTimeline{ + id:time_line + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + mode: FluTimelineType.Alternate + model:list_model + } + +} diff --git a/example/qml-Qt6/page/T_ToggleSwitch.qml b/example/qml-Qt6/page/T_ToggleSwitch.qml new file mode 100644 index 00000000..0a708f0a --- /dev/null +++ b/example/qml-Qt6/page/T_ToggleSwitch.qml @@ -0,0 +1,51 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"ToggleSwitch" + + FluArea{ + Layout.fillWidth: true + height: 68 + paddings: 10 + Layout.topMargin: 20 + Row{ + spacing: 30 + anchors.verticalCenter: parent.verticalCenter + FluToggleSwitch{ + disabled: toggle_switch.checked + } + FluToggleSwitch{ + disabled: toggle_switch.checked + text:"Right" + } + FluToggleSwitch{ + disabled: toggle_switch.checked + text:"Left" + textRight: false + } + } + FluToggleSwitch{ + id:toggle_switch + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text:"Disabled" + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluToggleSwitch{ + text:"Text" +}' + } + + +} diff --git a/example/qml-Qt6/page/T_Tooltip.qml b/example/qml-Qt6/page/T_Tooltip.qml new file mode 100644 index 00000000..1a05ef80 --- /dev/null +++ b/example/qml-Qt6/page/T_Tooltip.qml @@ -0,0 +1,103 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Tooltip" + + FluText{ + Layout.topMargin: 20 + text:"鼠标悬停不动,弹出Tooltip" + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 68 + paddings: 10 + + Column{ + spacing: 5 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluText{ + text:"FluIconButton的text属性自带Tooltip效果" + } + FluIconButton{ + iconSource:FluentIcons.ChromeCloseContrast + iconSize: 15 + text:"删除" + onClicked:{ + showSuccess("点击IconButton") + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluIconButton{ + iconSource:FluentIcons.ChromeCloseContrast + iconSize: 15 + text:"删除" + onClicked:{ + showSuccess("点击IconButton") + } +} +' + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 68 + paddings: 10 + + Column{ + spacing: 5 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + FluText{ + text:"给一个Button添加Tooltip效果" + } + FluButton{ + id:button_1 + text:"删除" + onClicked:{ + showSuccess("点击一个Button") + } + FluTooltip{ + visible: button_1.hovered + text:button_1.text + delay: 1000 + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluButton{ + id:button_1 + text:"删除" + FluTooltip{ + visible: button_1.hovered + text:button_1.text + delay: 1000 + } + onClicked:{ + showSuccess("点击一个Button") + } +}' + } + + +} diff --git a/example/qml-Qt6/page/T_Tour.qml b/example/qml-Qt6/page/T_Tour.qml new file mode 100644 index 00000000..65ad8aa1 --- /dev/null +++ b/example/qml-Qt6/page/T_Tour.qml @@ -0,0 +1,80 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Tour" + + FluTour{ + id:tour + steps:[ + {title:"Upload File",description: "Put your files here.",target:()=>btn_upload}, + {title:"Save",description: "Save your changes.",target:()=>btn_save}, + {title:"Other Actions",description: "Click to see other actions.",target:()=>btn_more} + ] + } + + FluArea{ + Layout.fillWidth: true + height: 130 + paddings: 10 + Layout.topMargin: 20 + + FluFilledButton{ + anchors{ + top: parent.top + topMargin: 14 + } + text:"Begin Tour" + onClicked: { + tour.open() + } + } + + Row{ + spacing: 20 + anchors{ + top: parent.top + topMargin: 60 + } + FluButton{ + id:btn_upload + text:"Upload" + onClicked: { + showInfo("Upload") + } + } + FluFilledButton{ + id:btn_save + text:"Save" + onClicked: { + showInfo("Save") + } + } + FluIconButton{ + id:btn_more + iconSource: FluentIcons.More + onClicked: { + showInfo("More") + } + } + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluTour{ + id:tour + steps:[ + {title:"Upload File",description: "Put your files here.",target:()=>btn_upload}, + {title:"Save",description: "Save your changes.",target:()=>btn_save}, + {title:"Other Actions",description: "Click to see other actions.",target:()=>btn_more} + ] +}' + } + +} diff --git a/example/qml-Qt6/page/T_TreeView.qml b/example/qml-Qt6/page/T_TreeView.qml new file mode 100644 index 00000000..80556354 --- /dev/null +++ b/example/qml-Qt6/page/T_TreeView.qml @@ -0,0 +1,156 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage { + + title:"TreeView" + + function randomName() { + var names = ["张三", "李四", "王五", "赵六", "钱七", "孙八", "周九", "吴十"] + return names[Math.floor(Math.random() * names.length)] + } + + function randomCompany() { + var companies = ["阿里巴巴", "腾讯", "百度", "京东", "华为", "小米", "字节跳动", "美团", "滴滴"] + return companies[Math.floor(Math.random() * companies.length)] + } + + function randomDepartment() { + var departments = ["技术部", "销售部", "市场部", "人事部", "财务部", "客服部", "产品部", "设计部", "运营部"] + return departments[Math.floor(Math.random() * departments.length)] + } + + function createEmployee() { + var name = randomName() + return tree_view.createItem(name, false) + } + + function createSubtree(numEmployees) { + var employees = [] + for (var i = 0; i < numEmployees; i++) { + employees.push(createEmployee()) + } + return tree_view.createItem(randomDepartment(), true, employees) + } + + function createOrg(numLevels, numSubtrees, numEmployees) { + if (numLevels === 0) { + return [] + } + var subtrees = [] + for (var i = 0; i < numSubtrees; i++) { + subtrees.push(createSubtree(numEmployees)) + } + return [tree_view.createItem(randomCompany(), true, subtrees)].concat(createOrg(numLevels - 1, numSubtrees, numEmployees)) + } + + FluArea{ + id:layout_actions + Layout.fillWidth: true + Layout.topMargin: 20 + height: 50 + paddings: 10 + RowLayout{ + spacing: 14 + FluDropDownButton{ + id:btn_selection_model + Layout.preferredWidth: 140 + text:"None" + FluMenuItem{ + text:"None" + onClicked: { + btn_selection_model.text = text + tree_view.selectionMode = FluTabViewType.Equal + } + } + FluMenuItem{ + text:"Single" + onClicked: { + btn_selection_model.text = text + tree_view.selectionMode = FluTabViewType.SizeToContent + } + } + FluMenuItem{ + text:"Muiltple" + onClicked: { + btn_selection_model.text = text + tree_view.selectionMode = FluTabViewType.Compact + } + } + } + FluFilledButton{ + text:"获取选中的数据" + onClicked: { + if(tree_view.selectionMode === FluTreeViewType.None){ + showError("当前非选择模式,没有选中的数据") + } + if(tree_view.selectionMode === FluTreeViewType.Single){ + if(!tree_view.signleData()){ + showError("没有选中数据") + return + } + showSuccess(tree_view.signleData().text) + } + if(tree_view.selectionMode === FluTreeViewType.Multiple){ + if(tree_view.multipData().length===0){ + showError("没有选中数据") + return + } + var info = [] + tree_view.multipData().map((value)=>info.push(value.text)) + showSuccess(info.join(",")) + } + } + } + } + } + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 10 + paddings: 10 + height: 400 + FluTreeView{ + id:tree_view + width:240 + anchors{ + top:parent.top + left:parent.left + bottom:parent.bottom + } + onItemClicked: + (model)=>{ + showSuccess(model.text) + } + + Component.onCompleted: { + var org = createOrg(3, 3, 3) + createItem() + updateData(org) + } + } + } + + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -1 + code:'FluTreeView{ + id:tree_view + width:240 + height:600 + Component.onCompleted: { + var datas = [] + datas.push(createItem("Node1",false)) + datas.push(createItem("Node2",false)) + datas.push(createItem("Node2",true,[createItem("Node2-1",false),createItem("Node2-2",false)])) + updateData(datas) + } +} +' + } + + +} diff --git a/example/qml-Qt6/page/T_Typography.qml b/example/qml-Qt6/page/T_Typography.qml new file mode 100644 index 00000000..61f3cf43 --- /dev/null +++ b/example/qml-Qt6/page/T_Typography.qml @@ -0,0 +1,84 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import FluentUI + +FluContentPage { + + property real textScale: 1 + + title: "Typography" + rightPadding: 10 + + FluArea{ + anchors{ + top:parent.top + left: parent.left + right: parent.right + bottom: parent.bottom + topMargin: 20 + } + paddings: 10 + ColumnLayout{ + spacing: 0 + scale: textScale + transformOrigin: Item.TopLeft + FluText{ + id:text_Display + text:"Display" + padding: 0 + font: FluTextStyle.Display + } + FluText{ + id:text_TitleLarge + text:"Title Large" + padding: 0 + font: FluTextStyle.TitleLarge + } + FluText{ + id:text_Title + text:"Title" + padding: 0 + font: FluTextStyle.Title + } + FluText{ + id:text_Subtitle + text:"Subtitle" + padding: 0 + font: FluTextStyle.Subtitle + } + FluText{ + id:text_BodyStrong + text:"Body Strong" + padding: 0 + font: FluTextStyle.BodyStrong + } + FluText{ + id:text_Body + text:"Body" + padding: 0 + font: FluTextStyle.Body + } + FluText{ + id:text_Caption + text:"Caption" + padding: 0 + font: FluTextStyle.Caption + } + } + + FluSlider{ + id:slider + orientation: Qt.Vertical + anchors{ + right: parent.right + rightMargin: 45 + top: parent.top + topMargin: 30 + } + onValueChanged:{ + textScale = 1+value/100 + } + } + } +} diff --git a/example/qml-Qt6/page/T_Watermark.qml b/example/qml-Qt6/page/T_Watermark.qml new file mode 100644 index 00000000..3b4aa275 --- /dev/null +++ b/example/qml-Qt6/page/T_Watermark.qml @@ -0,0 +1,132 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import FluentUI +import "qrc:///example/qml/component" + +FluContentPage{ + + title:"Watermark" + + FluArea{ + anchors.fill: parent + anchors.topMargin: 20 + + ColumnLayout{ + anchors{ + left: parent.left + leftMargin: 14 + } + + RowLayout{ + spacing: 10 + Layout.topMargin: 14 + FluText{ + text:"text:" + Layout.alignment: Qt.AlignVCenter + } + FluTextBox{ + id:text_box + text:"会磨刀的小猪" + } + } + + RowLayout{ + spacing: 10 + FluText{ + text:"textSize:" + Layout.alignment: Qt.AlignVCenter + } + FluSlider{ + id:slider_text_size + value: 20 + from: 13 + to:50 + } + } + RowLayout{ + spacing: 10 + FluText{ + text:"gapX:" + Layout.alignment: Qt.AlignVCenter + } + FluSlider{ + id:slider_gap_x + value: 100 + } + } + RowLayout{ + spacing: 10 + FluText{ + text:"gapY:" + Layout.alignment: Qt.AlignVCenter + } + FluSlider{ + id:slider_gap_y + value: 100 + } + } + RowLayout{ + spacing: 10 + FluText{ + text:"offsetX:" + Layout.alignment: Qt.AlignVCenter + } + FluSlider{ + id:slider_offset_x + value: 50 + } + } + RowLayout{ + spacing: 10 + FluText{ + text:"offsetY:" + Layout.alignment: Qt.AlignVCenter + } + FluSlider{ + id:slider_offset_y + value: 50 + } + } + RowLayout{ + spacing: 10 + FluText{ + text:"rotate:" + Layout.alignment: Qt.AlignVCenter + } + FluSlider{ + id:slider_rotate + value: 22 + from: 0 + to:360 + } + } + RowLayout{ + spacing: 10 + FluText{ + text:"textColor:" + Layout.alignment: Qt.AlignVCenter + } + FluColorPicker{ + id:color_picker + Component.onCompleted: { + setColor(Qt.rgba(0,0,0,0.1)) + } + } + } + } + + FluWatermark{ + id:water_mark + anchors.fill: parent + text:text_box.text + textColor: color_picker.colorValue + textSize: slider_text_size.value + rotate: slider_rotate.value + gap:Qt.point(slider_gap_x.value,slider_gap_y.value) + offset: Qt.point(slider_offset_x.value,slider_offset_y.value) + } + } + +} diff --git a/example/qml-Qt6/window/AboutWindow.qml b/example/qml-Qt6/window/AboutWindow.qml new file mode 100644 index 00000000..862923dd --- /dev/null +++ b/example/qml-Qt6/window/AboutWindow.qml @@ -0,0 +1,150 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import FluentUI +import "qrc:///example/qml/component" + +CustomWindow { + + id:window + title:"关于" + width: 600 + height: 600 + fixSize: true + launchMode: FluWindowType.SingleTask + + ColumnLayout{ + anchors{ + top: parent.top + left: parent.left + right: parent.right + } + + RowLayout{ + Layout.topMargin: 20 + Layout.leftMargin: 15 + spacing: 14 + FluText{ + text:"FluentUI" + font: FluTextStyle.Title + MouseArea{ + anchors.fill: parent + onClicked: { + FluApp.navigate("/") + } + } + } + FluText{ + text:"v%1".arg(appInfo.version) + font: FluTextStyle.Body + Layout.alignment: Qt.AlignBottom + } + } + + RowLayout{ + spacing: 14 + Layout.topMargin: 20 + Layout.leftMargin: 15 + FluText{ + text:"作者:" + } + FluText{ + text:"朱子楚" + Layout.alignment: Qt.AlignBottom + } + } + + RowLayout{ + spacing: 14 + Layout.leftMargin: 15 + FluText{ + text:"GitHub:" + } + FluTextButton{ + id:text_hublink + topPadding:0 + bottomPadding:0 + text:"https://github.com/zhuzichu520/FluentUI" + Layout.alignment: Qt.AlignBottom + onClicked: { + Qt.openUrlExternally(text_hublink.text) + } + } + } + + RowLayout{ + spacing: 14 + Layout.leftMargin: 15 + FluText{ + text:"B站:" + } + FluTextButton{ + topPadding:0 + bottomPadding:0 + text:"https://www.bilibili.com/video/BV1mg4y1M71w/" + Layout.alignment: Qt.AlignBottom + onClicked: { + Qt.openUrlExternally(text) + } + } + } + + RowLayout{ + spacing: 14 + Layout.leftMargin: 15 + FluText{ + id:text_info + text:"如果该项目对你有作用,就请点击上方链接给一个免费的star,或者一键三连,谢谢!" + ColorAnimation { + id: animation + target: text_info + property: "color" + from: "red" + to: "blue" + duration: 1000 + running: true + loops: Animation.Infinite + easing.type: Easing.InOutQuad + } + } + } + + RowLayout{ + spacing: 14 + Layout.topMargin: 20 + Layout.leftMargin: 15 + FluText{ + text:"捐赠:" + } + } + + Item{ + Layout.preferredWidth: parent.width + Layout.preferredHeight: 252 + Row{ + anchors.horizontalCenter: parent.horizontalCenter + spacing: 30 + Image{ + width: 250 + height: 250 + source: "qrc:/example/res/image/qrcode_wx.jpg" + } + Image{ + width: 250 + height: 250 + source: "qrc:/example/res/image/qrcode_zfb.jpg" + } + } + } + + RowLayout{ + spacing: 14 + Layout.leftMargin: 15 + Layout.topMargin: 20 + FluText{ + id:text_desc + text:"个人开发,维护不易,你们的捐赠就是我继续更新的动力!\n有什么问题提Issues,只要时间充足我就会解决的!!" + } + } + } +} diff --git a/example/qml-Qt6/window/HotloadWindow.qml b/example/qml-Qt6/window/HotloadWindow.qml new file mode 100644 index 00000000..31a94400 --- /dev/null +++ b/example/qml-Qt6/window/HotloadWindow.qml @@ -0,0 +1,85 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import FluentUI +import example +import "qrc:///example/qml/component" + +CustomWindow { + + id:window + title:"热加载" + width: 800 + height: 600 + minimumWidth: 520 + minimumHeight: 200 + launchMode: FluWindowType.SingleTask + FileWatcher{ + id:watcher + onFileChanged: { + loader.reload() + } + } + FluArea{ + anchors.fill: parent + FluRemoteLoader{ + id:loader + anchors.fill: parent + statusMode: FluStatusViewType.Success + lazy: true + errorItem: Item{ + FluText{ + text:loader.itemLodaer().sourceComponent.errorString() + color:"red" + anchors.fill: parent + wrapMode: Text.WrapAnywhere + padding: 20 + verticalAlignment: Qt.AlignVCenter + horizontalAlignment: Qt.AlignHCenter + } + } + } + FluText{ + text:"拖入qml文件" + font.pixelSize: 26 + anchors.centerIn: parent + visible: !loader.itemLodaer().item && loader.statusMode === FluStatusViewType.Success + } + Rectangle{ + radius: 4 + anchors.fill: parent + color: "#33333333" + visible: drop_area.containsDrag + } + DropArea{ + id:drop_area + anchors.fill: parent + onEntered: + (event)=>{ + if(!event.hasUrls){ + event.accepted = false + return + } + if (event.urls.length !== 1) { + event.accepted = false + return + } + var url = event.urls[0].toString() + var fileExtension = url.substring(url.lastIndexOf(".") + 1) + if (fileExtension !== "qml") { + event.accepted = false + return + } + return true + } + onDropped: + (event)=>{ + var path = event.urls[0].toString() + loader.source = path + watcher.path = path + loader.reload() + } + } + } + +} diff --git a/example/qml-Qt6/window/LoginWindow.qml b/example/qml-Qt6/window/LoginWindow.qml new file mode 100644 index 00000000..9f3e25f7 --- /dev/null +++ b/example/qml-Qt6/window/LoginWindow.qml @@ -0,0 +1,63 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import FluentUI +import "qrc:///example/qml/component" + +CustomWindow { + + id:window + title:"登录" + width: 400 + height: 400 + fixSize: true + + onInitArgument: + (argument)=>{ + textbox_uesrname.updateText(argument.username) + textbox_password.focus = true + } + + ColumnLayout{ + anchors{ + left: parent.left + right: parent.right + verticalCenter: parent.verticalCenter + } + + FluAutoSuggestBox{ + id:textbox_uesrname + items:[{title:"Admin"},{title:"User"}] + placeholderText: "请输入账号" + Layout.preferredWidth: 260 + Layout.alignment: Qt.AlignHCenter + } + + FluTextBox{ + id:textbox_password + Layout.topMargin: 20 + Layout.preferredWidth: 260 + placeholderText: "请输入密码" + echoMode:TextInput.Password + Layout.alignment: Qt.AlignHCenter + } + + FluFilledButton{ + text:"登录" + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 20 + onClicked:{ + if(textbox_password.text === ""){ + showError("请随便输入一个密码") + return + } + onResult({password:textbox_password.text}) + window.close() + } + } + + } + + + +} diff --git a/example/qml-Qt6/window/MainWindow.qml b/example/qml-Qt6/window/MainWindow.qml new file mode 100644 index 00000000..be1553d6 --- /dev/null +++ b/example/qml-Qt6/window/MainWindow.qml @@ -0,0 +1,280 @@ +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Layouts +import Qt.labs.platform +import FluentUI +import example +import "qrc:///example/qml/component" +import "qrc:///example/qml/global" + +CustomWindow { + + id:window + title: "FluentUI" + width: 1000 + height: 640 + closeDestory:false + minimumWidth: 520 + minimumHeight: 200 + appBarVisible: false + launchMode: FluWindowType.SingleTask + + closeFunc:function(event){ + dialog_close.open() + event.accepted = false + } + + Component.onCompleted: { + FluTools.setQuitOnLastWindowClosed(false) + tour.open() + } + + SystemTrayIcon { + id:system_tray + visible: true + icon.source: "qrc:/example/res/image/favicon.ico" + tooltip: "FluentUI" + menu: Menu { + MenuItem { + text: "退出" + onTriggered: { + window.deleteWindow() + FluApp.closeApp() + } + } + } + onActivated: + (reason)=>{ + if(reason === SystemTrayIcon.Trigger){ + window.show() + window.raise() + window.requestActivate() + } + } + } + + FluContentDialog{ + id:dialog_close + title:"退出" + message:"确定要退出程序吗?" + negativeText:"最小化" + buttonFlags: FluContentDialogType.NegativeButton | FluContentDialogType.NeutralButton | FluContentDialogType.PositiveButton + onNegativeClicked:{ + window.hide() + system_tray.showMessage("友情提示","FluentUI已隐藏至托盘,点击托盘可再次激活窗口"); + } + positiveText:"退出" + neutralText:"取消" + onPositiveClicked:{ + window.deleteWindow() + FluApp.closeApp() + } + } + + Flipable{ + id:flipable + anchors.fill: parent + property bool flipped: false + property real flipAngle: 0 + transform: Rotation { + id: rotation + origin.x: flipable.width/2 + origin.y: flipable.height/2 + axis { x: 0; y: 1; z: 0 } + angle: flipable.flipAngle + + } + states: State { + PropertyChanges { target: flipable; flipAngle: 180 } + when: flipable.flipped + } + transitions: Transition { + NumberAnimation { target: flipable; property: "flipAngle"; duration: 1000 ; easing.type: Easing.OutCubic} + } + back: Item{ + anchors.fill: flipable + visible: flipable.flipAngle !== 0 + FluAppBar { + anchors { + top: parent.top + left: parent.left + right: parent.right + } + darkText: lang.dark_mode + showDark: true + z:7 + darkClickListener:(button)=>handleDarkChanged(button) + } + Row{ + z:8 + anchors{ + top: parent.top + left: parent.left + topMargin: FluTools.isMacos() ? 20 : 5 + leftMargin: 5 + } + FluIconButton{ + iconSource: FluentIcons.ChromeBack + width: 30 + height: 30 + iconSize: 13 + onClicked: { + flipable.flipped = false + } + } + FluIconButton{ + iconSource: FluentIcons.Sync + width: 30 + height: 30 + iconSize: 13 + onClicked: { + loader.reload() + } + } + } + + FluRemoteLoader{ + id:loader + lazy: true + anchors.fill: parent + // source: "http://localhost:9000/RemoteComponent.qml" + source: "https://zhu-zichu.gitee.io/RemoteComponent.qml" + } + } + front: Item{ + id:page_front + visible: flipable.flipAngle !== 180 + anchors.fill: flipable + FluAppBar { + id:app_bar_front + anchors { + top: parent.top + left: parent.left + right: parent.right + } + darkText: lang.dark_mode + showDark: true + darkClickListener:(button)=>handleDarkChanged(button) + z:7 + } + FluNavigationView{ + property int clickCount: 0 + id:nav_view + width: parent.width + height: parent.height + z:999 + //Stack模式,每次切换都会将页面压入栈中,随着栈的页面增多,消耗的内存也越多,内存消耗多就会卡顿,这时候就需要按返回将页面pop掉,释放内存。该模式可以配合FluPage中的launchMode属性,设置页面的启动模式 + pageMode: FluNavigationViewType.Stack + //NoStack模式,每次切换都会销毁之前的页面然后创建一个新的页面,只需消耗少量内存(推荐) + // pageMode: FluNavigationViewType.NoStack + items: ItemsOriginal + footerItems:ItemsFooter + topPadding:FluTools.isMacos() ? 20 : 0 + displayMode:MainEvent.displayMode + logo: "qrc:/example/res/image/favicon.ico" + title:"FluentUI" + onLogoClicked:{ + clickCount += 1 + showSuccess("点击%1次".arg(clickCount)) + if(clickCount === 5){ + loader.reload() + flipable.flipped = true + clickCount = 0 + } + } + autoSuggestBox:FluAutoSuggestBox{ + width: 280 + anchors.centerIn: parent + iconSource: FluentIcons.Search + items: ItemsOriginal.getSearchData() + placeholderText: lang.search + onItemClicked: + (data)=>{ + ItemsOriginal.startPageByItem(data) + } + } + Component.onCompleted: { + ItemsOriginal.navigationView = nav_view + ItemsFooter.navigationView = nav_view + setCurrentIndex(0) + } + } + } + } + + Component{ + id:com_reveal + CircularReveal{ + id:reveal + target:window.contentItem + anchors.fill: parent + onAnimationFinished:{ + //动画结束后释放资源 + loader_reveal.sourceComponent = undefined + } + onImageChanged: { + changeDark() + } + } + } + + Loader{ + id:loader_reveal + anchors.fill: parent + } + + function distance(x1,y1,x2,y2){ + return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)) + } + + function handleDarkChanged(button){ + if(FluTools.isMacos() || !FluTheme.enableAnimation){ + changeDark() + }else{ + loader_reveal.sourceComponent = com_reveal + var target = window.contentItem + var pos = button.mapToItem(target,0,0) + var mouseX = pos.x + var mouseY = pos.y + var radius = Math.max(distance(mouseX,mouseY,0,0),distance(mouseX,mouseY,target.width,0),distance(mouseX,mouseY,0,target.height),distance(mouseX,mouseY,target.width,target.height)) + var reveal = loader_reveal.item + reveal.start(reveal.width*Screen.devicePixelRatio,reveal.height*Screen.devicePixelRatio,Qt.point(mouseX,mouseY),radius) + } + } + + function changeDark(){ + if(FluTheme.dark){ + FluTheme.darkMode = FluThemeType.Light + }else{ + FluTheme.darkMode = FluThemeType.Dark + } + } + + Shortcut { + sequence: "F5" + context: Qt.WindowShortcut + onActivated: { + if(flipable.flipped){ + loader.reload() + } + } + } + + Shortcut { + sequence: "F6" + context: Qt.WindowShortcut + onActivated: { + tour.open() + } + } + + FluTour{ + id:tour + steps:[ + {title:"夜间模式",description: "这里可以切换夜间模式.",target:()=>app_bar_front.darkButton()}, + {title:"隐藏彩蛋",description: "多点几下试试!!",target:()=>nav_view.logoButton()} + ] + } + +} diff --git a/example/qml-Qt6/window/SingleInstanceWindow.qml b/example/qml-Qt6/window/SingleInstanceWindow.qml new file mode 100644 index 00000000..6a765bbc --- /dev/null +++ b/example/qml-Qt6/window/SingleInstanceWindow.qml @@ -0,0 +1,35 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import FluentUI +import "qrc:///example/qml/component" + +CustomWindow { + + id:window + title:"SingleInstance" + width: 500 + height: 600 + fixSize: true + launchMode: FluWindowType.SingleInstance + + FluTextBox{ + anchors{ + top:parent.top + topMargin:60 + horizontalCenter: parent.horizontalCenter + } + } + + FluText{ + wrapMode: Text.WrapAnywhere + anchors{ + left: parent.left + right: parent.right + leftMargin: 20 + rightMargin: 20 + verticalCenter: parent.verticalCenter + } + text:"我是一个SingleInstance模式的窗口,如果我存在,我会销毁之前的窗口,并创建一个新窗口" + } +} diff --git a/example/qml-Qt6/window/SingleTaskWindow.qml b/example/qml-Qt6/window/SingleTaskWindow.qml new file mode 100644 index 00000000..7a312315 --- /dev/null +++ b/example/qml-Qt6/window/SingleTaskWindow.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import FluentUI +import "qrc:///example/qml/component" + +CustomWindow { + + id:window + title:"SingleTask" + width: 500 + height: 600 + fixSize: true + launchMode: FluWindowType.SingleTask + + FluText{ + anchors.centerIn: parent + text:"我是一个SingleTask模式的窗口,如果我存在,我就激活窗口" + } + +} diff --git a/example/qml-Qt6/window/StandardWindow.qml b/example/qml-Qt6/window/StandardWindow.qml new file mode 100644 index 00000000..aaee568e --- /dev/null +++ b/example/qml-Qt6/window/StandardWindow.qml @@ -0,0 +1,43 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import FluentUI +import "qrc:///example/qml/component" + +CustomWindow { + + id:window + title:"Standard" + width: 500 + height: 600 + fixSize: true + launchMode: FluWindowType.Standard + + FluMenuBar { + FluMenu { + title: qsTr("File") + Action { text: qsTr("New...") } + Action { text: qsTr("Open...") } + Action { text: qsTr("Save") } + Action { text: qsTr("Save As...") } + FluMenuSeparator { } + Action { text: qsTr("Quit") } + } + FluMenu { + title: qsTr("Edit") + Action { text: qsTr("Cut") } + Action { text: qsTr("Copy") } + Action { text: qsTr("Paste") } + } + FluMenu { + title: qsTr("Help") + Action { text: qsTr("About") } + } + } + + FluText{ + anchors.centerIn: parent + text:"我是一个Standard模式的窗口,每次我都会创建一个新的窗口" + } + +} diff --git a/example/qml/App.qml b/example/qml/App.qml index f0af5371..08960754 100644 --- a/example/qml/App.qml +++ b/example/qml/App.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Window -import QtQuick.Controls -import QtQuick.Layouts -import FluentUI +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 Window { id: app diff --git a/example/qml/component/CodeExpander.qml b/example/qml/component/CodeExpander.qml index f69c879a..5570c48b 100644 --- a/example/qml/component/CodeExpander.qml +++ b/example/qml/component/CodeExpander.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 FluExpander{ diff --git a/example/qml/component/CustomWindow.qml b/example/qml/component/CustomWindow.qml index 88451d28..626d1a39 100644 --- a/example/qml/component/CustomWindow.qml +++ b/example/qml/component/CustomWindow.qml @@ -1,7 +1,7 @@ -import QtQuick -import QtQuick.Layouts -import FluentUI -import org.wangwenx190.FramelessHelper +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 +import org.wangwenx190.FramelessHelper 1.0 FluWindow { id:window diff --git a/example/qml/global/ItemsFooter.qml b/example/qml/global/ItemsFooter.qml index c6742d06..463b52e2 100644 --- a/example/qml/global/ItemsFooter.qml +++ b/example/qml/global/ItemsFooter.qml @@ -1,7 +1,7 @@ pragma Singleton -import QtQuick -import FluentUI +import QtQuick 2.15 +import FluentUI 1.0 FluObject{ diff --git a/example/qml/global/ItemsOriginal.qml b/example/qml/global/ItemsOriginal.qml index 3f3beb48..5ebe0e96 100644 --- a/example/qml/global/ItemsOriginal.qml +++ b/example/qml/global/ItemsOriginal.qml @@ -1,7 +1,7 @@ pragma Singleton -import QtQuick -import FluentUI +import QtQuick 2.15 +import FluentUI 1.0 FluObject{ diff --git a/example/qml/global/MainEvent.qml b/example/qml/global/MainEvent.qml index 7119ffc6..c31e3a4a 100644 --- a/example/qml/global/MainEvent.qml +++ b/example/qml/global/MainEvent.qml @@ -1,8 +1,8 @@ pragma Singleton -import QtQuick -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 QtObject { property int displayMode : FluNavigationViewType.Auto diff --git a/example/qml/page/T_Acrylic.qml b/example/qml/page/T_Acrylic.qml index ae83a596..e23f239a 100644 --- a/example/qml/page/T_Acrylic.qml +++ b/example/qml/page/T_Acrylic.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Awesome.qml b/example/qml/page/T_Awesome.qml index 182ce910..1804475f 100644 --- a/example/qml/page/T_Awesome.qml +++ b/example/qml/page/T_Awesome.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 FluContentPage { diff --git a/example/qml/page/T_Badge.qml b/example/qml/page/T_Badge.qml index baccc3d7..8ce25806 100644 --- a/example/qml/page/T_Badge.qml +++ b/example/qml/page/T_Badge.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_BreadcrumbBar.qml b/example/qml/page/T_BreadcrumbBar.qml index f6a1ba08..bfe65baf 100644 --- a/example/qml/page/T_BreadcrumbBar.qml +++ b/example/qml/page/T_BreadcrumbBar.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Buttons.qml b/example/qml/page/T_Buttons.qml index 7707333f..699c8318 100644 --- a/example/qml/page/T_Buttons.qml +++ b/example/qml/page/T_Buttons.qml @@ -1,9 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import QtQuick.Controls.Basic -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_CalendarPicker.qml b/example/qml/page/T_CalendarPicker.qml index 4cb67901..c9d10574 100644 --- a/example/qml/page/T_CalendarPicker.qml +++ b/example/qml/page/T_CalendarPicker.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Captcha.qml b/example/qml/page/T_Captcha.qml index 8ac59e75..68466fce 100644 --- a/example/qml/page/T_Captcha.qml +++ b/example/qml/page/T_Captcha.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Carousel.qml b/example/qml/page/T_Carousel.qml index 75cc647c..de27ff74 100644 --- a/example/qml/page/T_Carousel.qml +++ b/example/qml/page/T_Carousel.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Chart.qml b/example/qml/page/T_Chart.qml index 7582b210..53906ba9 100644 --- a/example/qml/page/T_Chart.qml +++ b/example/qml/page/T_Chart.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_CheckBox.qml b/example/qml/page/T_CheckBox.qml index e3c6ea4a..28605536 100644 --- a/example/qml/page/T_CheckBox.qml +++ b/example/qml/page/T_CheckBox.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_ColorPicker.qml b/example/qml/page/T_ColorPicker.qml index 24954589..93375c3a 100644 --- a/example/qml/page/T_ColorPicker.qml +++ b/example/qml/page/T_ColorPicker.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_ComboBox.qml b/example/qml/page/T_ComboBox.qml index 15fba70b..d5463a7c 100644 --- a/example/qml/page/T_ComboBox.qml +++ b/example/qml/page/T_ComboBox.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_DatePicker.qml b/example/qml/page/T_DatePicker.qml index 0fe6c4df..ac978370 100644 --- a/example/qml/page/T_DatePicker.qml +++ b/example/qml/page/T_DatePicker.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Dialog.qml b/example/qml/page/T_Dialog.qml index b1e8b6b5..d4a14699 100644 --- a/example/qml/page/T_Dialog.qml +++ b/example/qml/page/T_Dialog.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Expander.qml b/example/qml/page/T_Expander.qml index 36f390e3..b9af09a3 100644 --- a/example/qml/page/T_Expander.qml +++ b/example/qml/page/T_Expander.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_FlipView.qml b/example/qml/page/T_FlipView.qml index 3baf06c1..843b9ab9 100644 --- a/example/qml/page/T_FlipView.qml +++ b/example/qml/page/T_FlipView.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Home.qml b/example/qml/page/T_Home.qml index c6acc672..52c23211 100644 --- a/example/qml/page/T_Home.qml +++ b/example/qml/page/T_Home.qml @@ -1,9 +1,9 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 import "qrc:///example/qml/global" -import FluentUI +import FluentUI 1.0 FluScrollablePage{ diff --git a/example/qml/page/T_Http.qml b/example/qml/page/T_Http.qml index 58942ade..b5f2123c 100644 --- a/example/qml/page/T_Http.qml +++ b/example/qml/page/T_Http.qml @@ -1,10 +1,10 @@ -import QtQuick -import Qt.labs.platform -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import QtQuick.Dialogs -import FluentUI +import QtQuick 2.15 +import Qt.labs.platform 1.1 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Dialogs 1.3 +import FluentUI 1.0 import "qrc:///example/qml/component" FluContentPage{ diff --git a/example/qml/page/T_Image.qml b/example/qml/page/T_Image.qml index bbeb6dcf..fb63d065 100644 --- a/example/qml/page/T_Image.qml +++ b/example/qml/page/T_Image.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_InfoBar.qml b/example/qml/page/T_InfoBar.qml index 24e3d907..ff5894a1 100644 --- a/example/qml/page/T_InfoBar.qml +++ b/example/qml/page/T_InfoBar.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Menu.qml b/example/qml/page/T_Menu.qml index fde64248..5b2f1808 100644 --- a/example/qml/page/T_Menu.qml +++ b/example/qml/page/T_Menu.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_MultiWindow.qml b/example/qml/page/T_MultiWindow.qml index 070fc4e4..88ff4137 100644 --- a/example/qml/page/T_MultiWindow.qml +++ b/example/qml/page/T_MultiWindow.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Pagination.qml b/example/qml/page/T_Pagination.qml index fa072dc6..838a4c9f 100644 --- a/example/qml/page/T_Pagination.qml +++ b/example/qml/page/T_Pagination.qml @@ -1,9 +1,9 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 import "qrc:///example/qml/component" -import FluentUI +import FluentUI 1.0 FluScrollablePage{ diff --git a/example/qml/page/T_Pivot.qml b/example/qml/page/T_Pivot.qml index 0fe52dfd..bd75554c 100644 --- a/example/qml/page/T_Pivot.qml +++ b/example/qml/page/T_Pivot.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Progress.qml b/example/qml/page/T_Progress.qml index cb4740b5..d5ded7de 100644 --- a/example/qml/page/T_Progress.qml +++ b/example/qml/page/T_Progress.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_QRCode.qml b/example/qml/page/T_QRCode.qml index 24f60f20..956c4d20 100644 --- a/example/qml/page/T_QRCode.qml +++ b/example/qml/page/T_QRCode.qml @@ -1,9 +1,9 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI -import Qt5Compat.GraphicalEffects +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 +import QtGraphicalEffects 1.15 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_RadioButton.qml b/example/qml/page/T_RadioButton.qml index 07b383b7..85307ab5 100644 --- a/example/qml/page/T_RadioButton.qml +++ b/example/qml/page/T_RadioButton.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_RatingControl.qml b/example/qml/page/T_RatingControl.qml index 4c39d380..05bd99a5 100644 --- a/example/qml/page/T_RatingControl.qml +++ b/example/qml/page/T_RatingControl.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage { diff --git a/example/qml/page/T_Rectangle.qml b/example/qml/page/T_Rectangle.qml index 9cfe42aa..346aeab4 100644 --- a/example/qml/page/T_Rectangle.qml +++ b/example/qml/page/T_Rectangle.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_RemoteLoader.qml b/example/qml/page/T_RemoteLoader.qml index 03dd73cf..c8e11c17 100644 --- a/example/qml/page/T_RemoteLoader.qml +++ b/example/qml/page/T_RemoteLoader.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluPage{ diff --git a/example/qml/page/T_Screenshot.qml b/example/qml/page/T_Screenshot.qml index 1a5cf38f..e146041b 100644 --- a/example/qml/page/T_Screenshot.qml +++ b/example/qml/page/T_Screenshot.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Settings.qml b/example/qml/page/T_Settings.qml index 8c2fcefd..57dadfe3 100644 --- a/example/qml/page/T_Settings.qml +++ b/example/qml/page/T_Settings.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/global" import "qrc:///example/qml/component" diff --git a/example/qml/page/T_Slider.qml b/example/qml/page/T_Slider.qml index 517904a6..138e265a 100644 --- a/example/qml/page/T_Slider.qml +++ b/example/qml/page/T_Slider.qml @@ -1,9 +1,9 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 import "qrc:///example/qml/component" -import FluentUI +import FluentUI 1.0 FluScrollablePage{ diff --git a/example/qml/page/T_StatusView.qml b/example/qml/page/T_StatusView.qml index 3a94578d..d84abc27 100644 --- a/example/qml/page/T_StatusView.qml +++ b/example/qml/page/T_StatusView.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_TabView.qml b/example/qml/page/T_TabView.qml index e50f4de1..dbdb9bcd 100644 --- a/example/qml/page/T_TabView.qml +++ b/example/qml/page/T_TabView.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_TableView.qml b/example/qml/page/T_TableView.qml index ffd6f174..26641d95 100644 --- a/example/qml/page/T_TableView.qml +++ b/example/qml/page/T_TableView.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluContentPage{ diff --git a/example/qml/page/T_Text.qml b/example/qml/page/T_Text.qml index c5624f62..43f43f13 100644 --- a/example/qml/page/T_Text.qml +++ b/example/qml/page/T_Text.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_TextBox.qml b/example/qml/page/T_TextBox.qml index 768fb481..5b717171 100644 --- a/example/qml/page/T_TextBox.qml +++ b/example/qml/page/T_TextBox.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Theme.qml b/example/qml/page/T_Theme.qml index a0b73877..4cd901dc 100644 --- a/example/qml/page/T_Theme.qml +++ b/example/qml/page/T_Theme.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_TimePicker.qml b/example/qml/page/T_TimePicker.qml index eefce75a..92bed2c5 100644 --- a/example/qml/page/T_TimePicker.qml +++ b/example/qml/page/T_TimePicker.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Timeline.qml b/example/qml/page/T_Timeline.qml index 3e7ae2b7..72a33665 100644 --- a/example/qml/page/T_Timeline.qml +++ b/example/qml/page/T_Timeline.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_ToggleSwitch.qml b/example/qml/page/T_ToggleSwitch.qml index 0a708f0a..79122cc3 100644 --- a/example/qml/page/T_ToggleSwitch.qml +++ b/example/qml/page/T_ToggleSwitch.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Tooltip.qml b/example/qml/page/T_Tooltip.qml index 1a05ef80..16e335a8 100644 --- a/example/qml/page/T_Tooltip.qml +++ b/example/qml/page/T_Tooltip.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_Tour.qml b/example/qml/page/T_Tour.qml index 65ad8aa1..74ea324c 100644 --- a/example/qml/page/T_Tour.qml +++ b/example/qml/page/T_Tour.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage{ diff --git a/example/qml/page/T_TreeView.qml b/example/qml/page/T_TreeView.qml index 80556354..c2604fc4 100644 --- a/example/qml/page/T_TreeView.qml +++ b/example/qml/page/T_TreeView.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Window -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluScrollablePage { diff --git a/example/qml/page/T_Typography.qml b/example/qml/page/T_Typography.qml index 61f3cf43..854eab56 100644 --- a/example/qml/page/T_Typography.qml +++ b/example/qml/page/T_Typography.qml @@ -1,7 +1,7 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 FluContentPage { diff --git a/example/qml/page/T_Watermark.qml b/example/qml/page/T_Watermark.qml index 3b4aa275..7c352683 100644 --- a/example/qml/page/T_Watermark.qml +++ b/example/qml/page/T_Watermark.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" FluContentPage{ diff --git a/example/qml/window/AboutWindow.qml b/example/qml/window/AboutWindow.qml index 862923dd..33fb6cdb 100644 --- a/example/qml/window/AboutWindow.qml +++ b/example/qml/window/AboutWindow.qml @@ -1,7 +1,7 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 import "qrc:///example/qml/component" CustomWindow { diff --git a/example/qml/window/HotloadWindow.qml b/example/qml/window/HotloadWindow.qml index 31a94400..07e918a0 100644 --- a/example/qml/window/HotloadWindow.qml +++ b/example/qml/window/HotloadWindow.qml @@ -1,8 +1,8 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import FluentUI -import example +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 +import example 1.0 import "qrc:///example/qml/component" CustomWindow { diff --git a/example/qml/window/LoginWindow.qml b/example/qml/window/LoginWindow.qml index 9f3e25f7..8ae713ec 100644 --- a/example/qml/window/LoginWindow.qml +++ b/example/qml/window/LoginWindow.qml @@ -1,7 +1,7 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import FluentUI +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 import "qrc:///example/qml/component" CustomWindow { diff --git a/example/qml/window/MainWindow.qml b/example/qml/window/MainWindow.qml index be1553d6..c34e31f7 100644 --- a/example/qml/window/MainWindow.qml +++ b/example/qml/window/MainWindow.qml @@ -1,10 +1,10 @@ -import QtQuick -import QtQuick.Window -import QtQuick.Controls -import QtQuick.Layouts -import Qt.labs.platform -import FluentUI -import example +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import Qt.labs.platform 1.1 +import FluentUI 1.0 +import example 1.0 import "qrc:///example/qml/component" import "qrc:///example/qml/global" diff --git a/example/qml/window/SingleInstanceWindow.qml b/example/qml/window/SingleInstanceWindow.qml index 6a765bbc..bfd95d88 100644 --- a/example/qml/window/SingleInstanceWindow.qml +++ b/example/qml/window/SingleInstanceWindow.qml @@ -1,7 +1,7 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 import "qrc:///example/qml/component" CustomWindow { diff --git a/example/qml/window/SingleTaskWindow.qml b/example/qml/window/SingleTaskWindow.qml index 7a312315..b3354da4 100644 --- a/example/qml/window/SingleTaskWindow.qml +++ b/example/qml/window/SingleTaskWindow.qml @@ -1,7 +1,7 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 import "qrc:///example/qml/component" CustomWindow { diff --git a/example/qml/window/StandardWindow.qml b/example/qml/window/StandardWindow.qml index aaee568e..848dae4d 100644 --- a/example/qml/window/StandardWindow.qml +++ b/example/qml/window/StandardWindow.qml @@ -1,7 +1,7 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import FluentUI +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 import "qrc:///example/qml/component" CustomWindow { diff --git a/example/resource.qrc b/example/resource.qrc new file mode 100644 index 00000000..252fbb0e --- /dev/null +++ b/example/resource.qrc @@ -0,0 +1,188 @@ + + + res/svg/avatar_1.svg + res/svg/avatar_2.svg + res/svg/avatar_3.svg + res/svg/avatar_4.svg + res/svg/avatar_5.svg + res/svg/avatar_6.svg + res/svg/avatar_7.svg + res/svg/avatar_8.svg + res/svg/avatar_9.svg + res/svg/avatar_10.svg + res/svg/avatar_11.svg + res/svg/avatar_12.svg + res/image/banner_1.jpg + res/image/banner_2.jpg + res/image/banner_3.jpg + res/image/logo_openai.png + res/image/favicon.ico + res/image/bg_home_header.png + res/image/ic_home_github.png + res/image/control/Acrylic.png + res/image/control/AnimatedIcon.png + res/image/control/AnimatedVisualPlayer.png + res/image/control/AnimationInterop.png + res/image/control/AppBarButton.png + res/image/control/AppBarSeparator.png + res/image/control/AppBarToggleButton.png + res/image/control/AutomationProperties.png + res/image/control/AutoSuggestBox.png + res/image/control/Border.png + res/image/control/BreadcrumbBar.png + res/image/control/Button.png + res/image/control/CalendarDatePicker.png + res/image/control/CalendarView.png + res/image/control/Canvas.png + res/image/control/Checkbox.png + res/image/control/Clipboard.png + res/image/control/ColorPaletteResources.png + res/image/control/ColorPicker.png + res/image/control/ComboBox.png + res/image/control/CommandBar.png + res/image/control/CommandBarFlyout.png + res/image/control/CompactSizing.png + res/image/control/ConnectedAnimation.png + res/image/control/ContentDialog.png + res/image/control/CreateMultipleWindows.png + res/image/control/DataGrid.png + res/image/control/DatePicker.png + res/image/control/DropDownButton.png + res/image/control/EasingFunction.png + res/image/control/Expander.png + res/image/control/FilePicker.png + res/image/control/FlipView.png + res/image/control/Flyout.png + res/image/control/Grid.png + res/image/control/GridView.png + res/image/control/HyperlinkButton.png + res/image/control/IconElement.png + res/image/control/Image.png + res/image/control/ImplicitTransition.png + res/image/control/InfoBadge.png + res/image/control/InfoBar.png + res/image/control/InkCanvas.png + res/image/control/InkToolbar.png + res/image/control/InputValidation.png + res/image/control/ItemsRepeater.png + res/image/control/Line.png + res/image/control/ListBox.png + res/image/control/ListView.png + res/image/control/MediaPlayerElement.png + res/image/control/MenuBar.png + res/image/control/MenuFlyout.png + res/image/control/NavigationView.png + res/image/control/NumberBox.png + res/image/control/PageTransition.png + res/image/control/ParallaxView.png + res/image/control/PasswordBox.png + res/image/control/PersonPicture.png + res/image/control/PipsPager.png + res/image/control/Pivot.png + res/image/control/ProgressBar.png + res/image/control/ProgressRing.png + res/image/control/PullToRefresh.png + res/image/control/RadialGradientBrush.png + res/image/control/RadioButton.png + res/image/control/RadioButtons.png + res/image/control/RatingControl.png + res/image/control/RelativePanel.png + res/image/control/RepeatButton.png + res/image/control/RevealFocus.png + res/image/control/RichEditBox.png + res/image/control/RichTextBlock.png + res/image/control/ScrollViewer.png + res/image/control/SemanticZoom.png + res/image/control/Shape.png + res/image/control/Slider.png + res/image/control/Sound.png + res/image/control/SplitButton.png + res/image/control/SplitView.png + res/image/control/StackPanel.png + res/image/control/StandardUICommand.png + res/image/control/SwipeControl.png + res/image/control/TabView.png + res/image/control/TeachingTip.png + res/image/control/TextBlock.png + res/image/control/TextBox.png + res/image/control/ThemeTransition.png + res/image/control/TimePicker.png + res/image/control/TitleBar.png + res/image/control/ToggleButton.png + res/image/control/ToggleSplitButton.png + res/image/control/ToggleSwitch.png + res/image/control/ToolTip.png + res/image/control/TreeView.png + res/image/control/VariableSizedWrapGrid.png + res/image/control/Viewbox.png + res/image/control/WebView.png + res/image/control/XamlUICommand.png + res/svg/home.svg + res/svg/home_dark.svg + res/image/qrcode_wx.jpg + res/image/qrcode_zfb.jpg + qml/App.qml + qml/component/CodeExpander.qml + qml/component/CustomWindow.qml + qml/global/ItemsFooter.qml + qml/global/ItemsOriginal.qml + qml/global/MainEvent.qml + qml/global/qmldir + qml/page/T_Acrylic.qml + qml/page/T_Awesome.qml + qml/page/T_Badge.qml + qml/page/T_BreadcrumbBar.qml + qml/page/T_Buttons.qml + qml/page/T_CalendarPicker.qml + qml/page/T_Captcha.qml + qml/page/T_Carousel.qml + qml/page/T_Chart.qml + qml/page/T_CheckBox.qml + qml/page/T_ColorPicker.qml + qml/page/T_ComboBox.qml + qml/page/T_DatePicker.qml + qml/page/T_Dialog.qml + qml/page/T_Expander.qml + qml/page/T_FlipView.qml + qml/page/T_Home.qml + qml/page/T_Http.qml + qml/page/T_Image.qml + qml/page/T_InfoBar.qml + qml/page/T_Menu.qml + qml/page/T_MultiWindow.qml + qml/page/T_Pagination.qml + qml/page/T_Pivot.qml + qml/page/T_Progress.qml + qml/page/T_QRCode.qml + qml/page/T_RadioButton.qml + qml/page/T_RatingControl.qml + qml/page/T_Rectangle.qml + qml/page/T_RemoteLoader.qml + qml/page/T_Screenshot.qml + qml/page/T_Settings.qml + qml/page/T_Slider.qml + qml/page/T_StatusView.qml + qml/page/T_TableView.qml + qml/page/T_TabView.qml + qml/page/T_Text.qml + qml/page/T_TextBox.qml + qml/page/T_Theme.qml + qml/page/T_Timeline.qml + qml/page/T_TimePicker.qml + qml/page/T_ToggleSwitch.qml + qml/page/T_Tooltip.qml + qml/page/T_Tour.qml + qml/page/T_TreeView.qml + qml/page/T_Typography.qml + qml/page/T_Watermark.qml + qml/window/AboutWindow.qml + qml/window/HotloadWindow.qml + qml/window/LoginWindow.qml + qml/window/MainWindow.qml + qml/window/SingleInstanceWindow.qml + qml/window/SingleTaskWindow.qml + qml/window/StandardWindow.qml + res/image/bg_scenic.png + res/image/image_1.jpg + + diff --git a/example/src/component/CircularReveal.h b/example/src/component/CircularReveal.h index 9262b044..14352ed2 100644 --- a/example/src/component/CircularReveal.h +++ b/example/src/component/CircularReveal.h @@ -12,7 +12,6 @@ class CircularReveal : public QQuickPaintedItem Q_OBJECT Q_PROPERTY_AUTO(QQuickItem*,target) Q_PROPERTY_AUTO(int,radius) - QML_NAMED_ELEMENT(CircularReveal) public: CircularReveal(QQuickItem* parent = nullptr); void paint(QPainter* painter) override; diff --git a/example/src/component/FileWatcher.h b/example/src/component/FileWatcher.h index 5db1df83..d3bb95cc 100644 --- a/example/src/component/FileWatcher.h +++ b/example/src/component/FileWatcher.h @@ -10,7 +10,6 @@ class FileWatcher : public QObject { Q_OBJECT Q_PROPERTY_AUTO(QString,path); - QML_NAMED_ELEMENT(FileWatcher) public: explicit FileWatcher(QObject *parent = nullptr); Q_SIGNAL void fileChanged(); diff --git a/example/src/main.cpp b/example/src/main.cpp index 9aec80a6..a9562905 100644 --- a/example/src/main.cpp +++ b/example/src/main.cpp @@ -9,6 +9,8 @@ #include #include #include "AppInfo.h" +#include "src/component/CircularReveal.h" +#include "src/component/FileWatcher.h" FRAMELESSHELPER_USE_NAMESPACE @@ -38,6 +40,8 @@ FRAMELESSHELPER_USE_NAMESPACE #ifdef FLUENTUI_BUILD_STATIC_LIB engine.addImportPath("qrc:/"); // 让静态资源可以被QML引擎搜索到 #endif + qmlRegisterType("example", 1, 0, "CircularReveal"); + qmlRegisterType("example", 1, 0, "FileWatcher"); appInfo->init(&engine); const QUrl url(QStringLiteral("qrc:/example/qml/App.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, diff --git a/example/src/stdafx.h b/example/src/stdafx.h index 0fe92d6d..a7581723 100644 --- a/example/src/stdafx.h +++ b/example/src/stdafx.h @@ -1,6 +1,6 @@ -#if defined(_MSC_VER) && (_MSC_VER >= 1600) -#pragma execution_character_set("utf-8") -#endif +//#if defined(_MSC_VER) && (_MSC_VER >= 1600) +//#pragma execution_character_set("utf-8") +//#endif #ifndef STDAFX_H #define STDAFX_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bee3c697..931a4cfe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,7 +11,8 @@ if(APPLE) set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) endif() -find_package(Qt6 REQUIRED COMPONENTS Core Quick Qml) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Quick Qml) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Quick Qml) if(QT_VERSION VERSION_GREATER_EQUAL "6.3") qt_standard_project_setup() @@ -28,25 +29,34 @@ foreach(filepath ${CPP_FILES}) list(APPEND sources_files ${filename}) endforeach(filepath) -#遍历所有qml文件 -file(GLOB_RECURSE QML_PATHS *.qml) -foreach(filepath ${QML_PATHS}) - string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" filename ${filepath}) - list(APPEND qml_files ${filename}) -endforeach(filepath) +if(QT_VERSION VERSION_GREATER_EQUAL "6.2") + #删除fluentuiplugin.cpp与fluentuiplugin.h,这些只要Qt5使用,Qt6不需要 + list(REMOVE_ITEM sources_files fluentuiplugin.h fluentuiplugin.cpp) -#遍历所有资源文件 -file(GLOB_RECURSE RES_PATHS *.png *.jpg *.svg *.ico *.ttf *.webp *.js) -foreach(filepath ${RES_PATHS}) - string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" filename ${filepath}) - list(APPEND resource_files ${filename}) -endforeach(filepath) + #遍历所有qml文件 + file(GLOB_RECURSE QML_PATHS *.qml) + foreach(filepath ${QML_PATHS}) + if(${filepath} MATCHES "Qt${QT_VERSION_MAJOR}/") + string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" filename ${filepath}) + list(APPEND qml_files ${filename}) + endif() + endforeach(filepath) -#修改资源文件导出路径 -foreach(filepath IN LISTS qml_files resource_files) - string(REPLACE "imports/FluentUI/" "" filename ${filepath}) - set_source_files_properties(${filepath} PROPERTIES QT_RESOURCE_ALIAS ${filename}) -endforeach() + #遍历所有资源文件 + file(GLOB_RECURSE RES_PATHS *.png *.jpg *.svg *.ico *.ttf *.webp *.js) + foreach(filepath ${RES_PATHS}) + if(${filepath} MATCHES "Qt${QT_VERSION_MAJOR}/") + string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" filename ${filepath}) + list(APPEND resource_files ${filename}) + endif() + endforeach(filepath) + + #修改资源文件导出路径 + foreach(filepath IN LISTS qml_files resource_files) + string(REPLACE "Qt${QT_VERSION_MAJOR}/imports/FluentUI/" "" filename ${filepath}) + set_source_files_properties(${filepath} PROPERTIES QT_RESOURCE_ALIAS ${filename}) + endforeach() +endif() #添加qml模块 if (FLUENTUI_BUILD_STATIC_LIB) @@ -54,7 +64,6 @@ if (FLUENTUI_BUILD_STATIC_LIB) else() set(LIB_TYPE "SHARED") endif() -qt_add_library(${PROJECT_NAME} ${LIB_TYPE}) if (FLUENTUI_BUILD_STATIC_LIB) set(PLUGIN_TARGET_NAME "") @@ -73,24 +82,38 @@ if(WIN32) ) endif() -qt_add_qml_module(${PROJECT_NAME} - PLUGIN_TARGET ${PLUGIN_TARGET_NAME} - OUTPUT_DIRECTORY ${FLUENTUI_QML_PLUGIN_DIRECTORY} - VERSION 1.0 - URI "FluentUI" - #修改qmltypes文件名称。默认fluentuiplugin.qmltypes,使用默认名称有时候import FluentUI会爆红,所以修改成plugins.qmltypes - TYPEINFO "plugins.qmltypes" - SOURCES ${sources_files} ${FLUENTUI_VERSION_RC_PATH} - QML_FILES ${qml_files} - RESOURCES ${resource_files} - RESOURCE_PREFIX "/" -) +if(QT_VERSION VERSION_GREATER_EQUAL "6.2") + qt_add_library(${PROJECT_NAME} ${LIB_TYPE}) + qt_add_qml_module(${PROJECT_NAME} + PLUGIN_TARGET ${PLUGIN_TARGET_NAME} + OUTPUT_DIRECTORY ${FLUENTUI_QML_PLUGIN_DIRECTORY} + VERSION 1.0 + URI "FluentUI" + #修改qmltypes文件名称。默认fluentuiplugin.qmltypes,使用默认名称有时候import FluentUI 1.0会爆红,所以修改成plugins.qmltypes + TYPEINFO "plugins.qmltypes" + SOURCES ${sources_files} ${FLUENTUI_VERSION_RC_PATH} + QML_FILES ${qml_files} + RESOURCES ${resource_files} + RESOURCE_PREFIX "/" + ) +else() + include(QmlPlugin) + add_qmlplugin(${PROJECT_NAME} + URI "FluentUI" + VERSION 1.0 + SOURCES ${sources_files} ${FLUENTUI_VERSION_RC_PATH} resource.qrc + QMLFILES ${qml_files} + QMLDIR imports/FluentUI + BINARY_DIR ${FLUENTUI_QML_PLUGIN_DIRECTORY} + LIBTYPE ${LIB_TYPE} + ) +endif() #链接库 target_link_libraries(${PROJECT_NAME} PUBLIC - Qt::CorePrivate - Qt::QuickPrivate - Qt::QmlPrivate + Qt${QT_VERSION_MAJOR}::CorePrivate + Qt${QT_VERSION_MAJOR}::QuickPrivate + Qt${QT_VERSION_MAJOR}::QmlPrivate ZXing ) diff --git a/src/Def.h b/src/Def.h index c40cf066..a77e8fd2 100644 --- a/src/Def.h +++ b/src/Def.h @@ -15,7 +15,6 @@ Q_ENUM_NS(CaptrueMode) QML_NAMED_ELEMENT(FluScreenshotType) } - namespace FluThemeType { Q_NAMESPACE enum DarkMode { diff --git a/src/FluTextStyle.cpp b/src/FluTextStyle.cpp index 4c6a153f..fa35cc84 100644 --- a/src/FluTextStyle.cpp +++ b/src/FluTextStyle.cpp @@ -1,5 +1,16 @@ #include "FluTextStyle.h" +FluTextStyle* FluTextStyle::m_instance = nullptr; + +FluTextStyle *FluTextStyle::getInstance() +{ + if(FluTextStyle::m_instance == nullptr){ + FluTextStyle::m_instance = new FluTextStyle; + } + return FluTextStyle::m_instance; +} + + FluTextStyle::FluTextStyle(QObject *parent) : QObject{parent} { diff --git a/src/FluTextStyle.h b/src/FluTextStyle.h index de10fa4f..7a8524e0 100644 --- a/src/FluTextStyle.h +++ b/src/FluTextStyle.h @@ -10,7 +10,6 @@ class FluTextStyle : public QObject { Q_OBJECT public: - explicit FluTextStyle(QObject *parent = nullptr); Q_PROPERTY_AUTO(QFont,Caption); Q_PROPERTY_AUTO(QFont,Body); Q_PROPERTY_AUTO(QFont,BodyStrong); @@ -20,8 +19,15 @@ public: Q_PROPERTY_AUTO(QFont,Display); QML_NAMED_ELEMENT(FluTextStyle) QML_SINGLETON -signals: - +private: + explicit FluTextStyle(QObject *parent = nullptr); + static FluTextStyle* m_instance; +public: + static FluTextStyle *getInstance(); + static FluTextStyle *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) + { + return getInstance(); + } }; #endif // FLUTEXTSTYLE_H diff --git a/src/FluTools.cpp b/src/FluTools.cpp index b7876fab..0decf35d 100644 --- a/src/FluTools.cpp +++ b/src/FluTools.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -123,3 +124,7 @@ QString FluTools::getApplicationDirPath(){ QUrl FluTools::getUrlByFilePath(const QString& path){ return QUrl::fromLocalFile(path); } + +QColor FluTools::colorAlpha(const QColor& color,qreal alpha){ + return QColor(color.red(),color.green(),color.blue(),255*alpha); +} diff --git a/src/FluTools.h b/src/FluTools.h index 5d65fa4b..1da83f5a 100644 --- a/src/FluTools.h +++ b/src/FluTools.h @@ -3,6 +3,7 @@ #include #include +#include #include /** @@ -135,6 +136,13 @@ public: */ Q_INVOKABLE QUrl getUrlByFilePath(const QString& path); + /** + * @brief colorAlpha + * @param color + * @param alpha + * @return + */ + Q_INVOKABLE QColor colorAlpha(const QColor&,qreal alpha); }; diff --git a/src/Qt5/imports/FluentUI/Controls/ColorPicker/ColorPicker.qml b/src/Qt5/imports/FluentUI/Controls/ColorPicker/ColorPicker.qml new file mode 100644 index 00000000..cb43e531 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/ColorPicker/ColorPicker.qml @@ -0,0 +1,233 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import "Content" + +Rectangle { + id: colorPicker + property color colorValue: "transparent" + property bool enableAlphaChannel: true + property bool enableDetails: true + property int colorHandleRadius : 8 + property color _changingColorValue : _hsla(hueSlider.value, sbPicker.saturation,sbPicker.brightness, alphaSlider.value) + on_ChangingColorValueChanged: { + colorValue = _changingColorValue + } + + signal colorChanged(color changedColor) + + implicitWidth: picker.implicitWidth + implicitHeight: picker.implicitHeight + color: "#00000000" + clip: true + + RowLayout { + id: picker + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: colorHandleRadius + anchors.bottom: parent.bottom + spacing: 0 + + + SBPicker { + id: sbPicker + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumWidth: 200 + Layout.minimumHeight: 200 + hueColor: { + var v = 1.0-hueSlider.value + if(0.0 <= v && v < 0.16) { + return Qt.rgba(1.0, 0.0, v/0.16, 1.0) + } else if(0.16 <= v && v < 0.33) { + return Qt.rgba(1.0 - (v-0.16)/0.17, 0.0, 1.0, 1.0) + } else if(0.33 <= v && v < 0.5) { + return Qt.rgba(0.0, ((v-0.33)/0.17), 1.0, 1.0) + } else if(0.5 <= v && v < 0.76) { + return Qt.rgba(0.0, 1.0, 1.0 - (v-0.5)/0.26, 1.0) + } else if(0.76 <= v && v < 0.85) { + return Qt.rgba((v-0.76)/0.09, 1.0, 0.0, 1.0) + } else if(0.85 <= v && v <= 1.0) { + return Qt.rgba(1.0, 1.0 - (v-0.85)/0.15, 0.0, 1.0) + } else { + return "red" + } + } + } + + Item { + id: huePicker + width: 12 + Layout.fillHeight: true + Layout.topMargin: colorHandleRadius + Layout.bottomMargin: colorHandleRadius + + Rectangle { + anchors.fill: parent + id: colorBar + gradient: Gradient { + GradientStop { position: 1.0; color: "#FF0000" } + GradientStop { position: 0.85; color: "#FFFF00" } + GradientStop { position: 0.76; color: "#00FF00" } + GradientStop { position: 0.5; color: "#00FFFF" } + GradientStop { position: 0.33; color: "#0000FF" } + GradientStop { position: 0.16; color: "#FF00FF" } + GradientStop { position: 0.0; color: "#FF0000" } + } + } + ColorSlider { + id: hueSlider; anchors.fill: parent + } + } + + Item { + id: alphaPicker + visible: enableAlphaChannel + width: 12 + Layout.leftMargin: 4 + Layout.fillHeight: true + Layout.topMargin: colorHandleRadius + Layout.bottomMargin: colorHandleRadius + Checkerboard { cellSide: 4 } + Rectangle { + anchors.fill: parent + gradient: Gradient { + GradientStop { position: 0.0; color: "#FF000000" } + GradientStop { position: 1.0; color: "#00000000" } + } + } + ColorSlider { + id: alphaSlider; anchors.fill: parent + } + } + + Column { + id: detailColumn + Layout.leftMargin: 4 + Layout.fillHeight: true + Layout.topMargin: colorHandleRadius + Layout.bottomMargin: colorHandleRadius + Layout.alignment: Qt.AlignRight + visible: enableDetails + + PanelBorder { + width: parent.width + height: 30 + visible: enableAlphaChannel + Checkerboard { cellSide: 5 } + Rectangle { + width: parent.width; height: 30 + border.width: 1; border.color: "black" + color: colorPicker.colorValue + } + } + + Item { + width: parent.width + height: 1 + } + + + PanelBorder { + id: colorEditBox + height: 15; width: parent.width + TextInput { + anchors.fill: parent + color: "#AAAAAA" + selectionColor: "#FF7777AA" + font.pixelSize: 11 + maximumLength: 9 + focus: false + text: _fullColorString(colorPicker.colorValue, alphaSlider.value) + selectByMouse: true + } + } + + Item { + width: parent.width + height: 8 + } + + Column { + width: parent.width + spacing: 1 + NumberBox { caption: "H:"; value: hueSlider.value.toFixed(2) } + NumberBox { caption: "S:"; value: sbPicker.saturation.toFixed(2) } + NumberBox { caption: "B:"; value: sbPicker.brightness.toFixed(2) } + } + + Item { + width: parent.width + height: 8 + } + + Column { + width: parent.width + spacing: 1 + NumberBox { + caption: "R:" + value: _getChannelStr(colorPicker.colorValue, 0) + min: 0; max: 255 + } + NumberBox { + caption: "G:" + value: _getChannelStr(colorPicker.colorValue, 1) + min: 0; max: 255 + } + NumberBox { + caption: "B:" + value: _getChannelStr(colorPicker.colorValue, 2) + min: 0; max: 255 + } + } + + Item{ + width: parent.width + height: 1 + } + + NumberBox { + visible: enableAlphaChannel + caption: "A:"; value: Math.ceil(alphaSlider.value*255) + min: 0; max: 255 + } + } + } + + function _hsla(h, s, b, a) { + var lightness = (2 - s)*b + var satHSL = s*b/((lightness <= 1) ? lightness : 2 - lightness) + lightness /= 2 + + var c = Qt.hsla(h, satHSL, lightness, a) + + colorChanged(c) + + return c + } + + function _rgb(rgb, a) { + + var c = Qt.rgba(rgb.r, rgb.g, rgb.b, a) + + colorChanged(c) + + return c + } + + function _fullColorString(clr, a) { + return "#" + ((Math.ceil(a*255) + 256).toString(16).substr(1, 2) + clr.toString().substr(1, 6)).toUpperCase() + } + + function _getChannelStr(clr, channelIdx) { + return parseInt(clr.toString().substr(channelIdx*2 + 1, 2), 16) + } + + function setColor(color) { + var c = Qt.tint(color, "transparent") + alphaSlider.setValue(c.a) + colorPicker.colorValue = c + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/Checkerboard.qml b/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/Checkerboard.qml new file mode 100644 index 00000000..302434ec --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/Checkerboard.qml @@ -0,0 +1,16 @@ +import QtQuick 2.15 +Grid { + id: root + property int cellSide: 5 + anchors.fill: parent + rows: height/cellSide; columns: width/cellSide + clip: true + Repeater { + model: root.columns*root.rows + Rectangle { + width: root.cellSide; height: root.cellSide + color: (index%2 == 0) ? "gray" : "white" + } + } +} + diff --git a/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/ColorSlider.qml b/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/ColorSlider.qml new file mode 100644 index 00000000..55157ae5 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/ColorSlider.qml @@ -0,0 +1,44 @@ +import QtQuick 2.15 + +Item { + property int cursorHeight: 7 + property real value: (1 - pickerCursor.y/height) + width: 15 + height: 300 + Item { + id: pickerCursor + width: parent.width + Rectangle { + x: -3; y: -height*0.5 + width: parent.width + 4; height: cursorHeight + border.color: "black"; border.width: 1 + color: "transparent" + Rectangle { + anchors.fill: parent; anchors.margins: 2 + border.color: "white"; border.width: 1 + color: "transparent" + } + } + } + MouseArea { + y: -Math.round(cursorHeight/2) + height: parent.height+cursorHeight + anchors.left: parent.left + preventStealing: true + anchors.right: parent.right + function handleMouse(mouse) { + if (mouse.buttons & Qt.LeftButton) { + pickerCursor.y = Math.max(0, Math.min(height, mouse.y)-cursorHeight) + } + } + onPositionChanged:(mouse)=> { + handleMouse(mouse) + } + onPressed:(mouse)=> handleMouse(mouse) + } + + function setValue(val) { + pickerCursor.y = height * (1 - val) + } +} + diff --git a/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/NumberBox.qml b/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/NumberBox.qml new file mode 100644 index 00000000..f4afb9db --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/NumberBox.qml @@ -0,0 +1,39 @@ +import QtQuick 2.15 + +Row { + property alias caption: captionBox.text + property alias value: inputBox.text + property alias min: numValidator.bottom + property alias max: numValidator.top + property alias decimals: numValidator.decimals + + width: 80; + height: 15 + spacing: 4 + //anchors.margins: 2 + Text { + id: captionBox + width: 18; height: parent.height + color: "#AAAAAA" + font.pixelSize: 11; font.bold: true + } + PanelBorder { + height: parent.height + TextInput { + id: inputBox + color: "#AAAAAA"; selectionColor: "#FF7777AA" + font.pixelSize: 11 + maximumLength: 10 + focus: false + readOnly: true + selectByMouse: true + validator: DoubleValidator { + id: numValidator + bottom: 0; top: 1; decimals: 2 + notation: DoubleValidator.StandardNotation + } + } + } +} + + diff --git a/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/PanelBorder.qml b/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/PanelBorder.qml new file mode 100644 index 00000000..67de5b5c --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/PanelBorder.qml @@ -0,0 +1,16 @@ +import QtQuick 2.15 + +Rectangle { + width : 40; height : 15; radius: 2 + border.width: 1; border.color: "#FF101010" + color: "transparent" + anchors.leftMargin: 1; anchors.topMargin: 3 + clip: true + Rectangle { + anchors.fill: parent; radius: 2 + anchors.leftMargin: -1; anchors.topMargin: -1 + anchors.rightMargin: 0; anchors.bottomMargin: 0 + border.width: 1; border.color: "#FF525255" + color: "transparent" + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/SBPicker.qml b/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/SBPicker.qml new file mode 100644 index 00000000..dfe31b52 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/ColorPicker/Content/SBPicker.qml @@ -0,0 +1,62 @@ +import QtQuick 2.15 + +Item { + id: root + property color hueColor : "blue" + property real saturation : pickerCursor.x/(width-2*r) + property real brightness : 1 - pickerCursor.y/(height-2*r) + property int r : colorHandleRadius + + Rectangle { + x : r + y : r + parent.height - 2 * r + rotation: -90 + transformOrigin: Item.TopLeft + width: parent.height - 2 * r + height: parent.width - 2 * r + gradient: Gradient { + GradientStop { position: 0.0; color: "#FFFFFF" } + GradientStop { position: 1.0; color: root.hueColor } + } + } + Rectangle { + x: r + y: r + width: parent.width - 2 * r + height: parent.height - 2 * r + gradient: Gradient { + GradientStop { position: 1.0; color: "#FF000000" } + GradientStop { position: 0.0; color: "#00000000" } + } + } + Item { + id: pickerCursor + Rectangle { + width: r*2; height: r*2 + radius: r + border.color: "black"; border.width: 2 + color: "transparent" + Rectangle { + anchors.fill: parent; anchors.margins: 2; + border.color: "white"; border.width: 2 + radius: width/2 + color: "transparent" + } + } + } + MouseArea { + anchors.fill: parent + x: r + y: r + preventStealing: true + function handleMouse(mouse) { + if (mouse.buttons & Qt.LeftButton) { + pickerCursor.x = Math.max(0,Math.min(mouse.x - r,width-2*r)); + pickerCursor.y = Math.max(0,Math.min(mouse.y - r,height-2*r)); + } + } + onPositionChanged:(mouse)=> handleMouse(mouse) + onPressed:(mouse)=> handleMouse(mouse) + } +} + diff --git a/src/Qt5/imports/FluentUI/Controls/FluAcrylic.qml b/src/Qt5/imports/FluentUI/Controls/FluAcrylic.qml new file mode 100644 index 00000000..c4100d03 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluAcrylic.qml @@ -0,0 +1,40 @@ +import QtQuick 2.15 +import QtGraphicalEffects 1.15 +import FluentUI 1.0 + +FluItem { + id: control + property color tintColor: Qt.rgba(1,1,1,1) + property real tintOpacity: 0.65 + property real luminosity: 0.01 + property real noiseOpacity : 0.066 + property alias target : effect_source.sourceItem + property int blurRadius: 32 + property rect targetRect : Qt.rect(control.x, control.y, control.width, control.height) + ShaderEffectSource { + id: effect_source + anchors.fill: parent + visible: false + sourceRect: control.targetRect + } + FastBlur { + id:fast_blur + anchors.fill: parent + source: effect_source + radius: control.blurRadius + } + Rectangle{ + anchors.fill: parent + color: Qt.rgba(1, 1, 1, luminosity) + } + Rectangle{ + anchors.fill: parent + color: Qt.rgba(tintColor.r, tintColor.g, tintColor.b, tintOpacity) + } + Image{ + anchors.fill: parent + source: "../Image/noise.png" + fillMode: Image.Tile + opacity: control.noiseOpacity + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluAppBar.qml b/src/Qt5/imports/FluentUI/Controls/FluAppBar.qml new file mode 100644 index 00000000..2720bc43 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluAppBar.qml @@ -0,0 +1,166 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Window 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 + +Rectangle{ + property string title: "" + property string darkText : "夜间模式" + property string minimizeText : "最小化" + property string restoreText : "向下还原" + property string maximizeText : "最大化" + property string closeText : "关闭" + property color textColor: FluTheme.dark ? "#FFFFFF" : "#000000" + property color minimizeNormalColor: Qt.rgba(0,0,0,0) + property color minimizeHoverColor: FluTheme.dark ? Qt.rgba(1,1,1,0.1) : Qt.rgba(0,0,0,0.06) + property color maximizeNormalColor: Qt.rgba(0,0,0,0) + property color maximizeHoverColor: FluTheme.dark ? Qt.rgba(1,1,1,0.1) : Qt.rgba(0,0,0,0.06) + property color closeNormalColor: Qt.rgba(0,0,0,0) + property color closeHoverColor: Qt.rgba(251/255,115/255,115/255,1) + property bool showDark: false + property bool showClose: true + property bool showMinimize: true + property bool showMaximize: true + property bool titleVisible: true + property url icon + property int iconSize: 20 + property bool isMac: FluTools.isMacos() + property color borerlessColor : FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark + property var maxClickListener : function(){ + if (d.win.visibility === Window.Maximized) + d.win.visibility = Window.Windowed + else + d.win.visibility = Window.Maximized + } + property var minClickListener: function(){ + d.win.visibility = Window.Minimized + } + property var closeClickListener : function(){ + d.win.close() + } + property var darkClickListener: function(){ + if(FluTheme.dark){ + FluTheme.darkMode = FluThemeType.Light + }else{ + FluTheme.darkMode = FluThemeType.Dark + } + } + id:control + color: Qt.rgba(0,0,0,0) + height: visible ? 30 : 0 + opacity: visible + z: 65535 + Item{ + id:d + property var win: Window.window + property bool isRestore: win && Window.Maximized === win.visibility + property bool resizable: win && !(win.minimumHeight === win.maximumHeight && win.maximumWidth === win.minimumWidth) + } + TapHandler { + onTapped: if (tapCount === 2) btn_maximize.clicked() + gesturePolicy: TapHandler.DragThreshold + } + DragHandler { + target: null + grabPermissions: TapHandler.CanTakeOverFromAnything + onActiveChanged: if (active) { d.win.startSystemMove(); } + } + Row{ + anchors{ + verticalCenter: parent.verticalCenter + left: isMac ? undefined : parent.left + leftMargin: isMac ? undefined : 10 + horizontalCenter: isMac ? parent.horizontalCenter : undefined + } + spacing: 10 + Image{ + width: control.iconSize + height: control.iconSize + visible: status === Image.Ready ? true : false + source: control.icon + anchors.verticalCenter: parent.verticalCenter + } + FluText { + text: title + visible: control.titleVisible + color:control.textColor + anchors.verticalCenter: parent.verticalCenter + } + } + RowLayout{ + anchors.right: parent.right + height: control.height + spacing: 0 + FluToggleSwitch{ + id:btn_dark + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: 5 + visible: showDark + text:darkText + textColor:control.textColor + checked: FluTheme.dark + textRight: false + clickListener:()=> darkClickListener(btn_dark) + } + FluIconButton{ + id:btn_minimize + Layout.preferredWidth: 40 + Layout.preferredHeight: 30 + iconSource : FluentIcons.ChromeMinimize + Layout.alignment: Qt.AlignVCenter + iconSize: 11 + text:minimizeText + radius: 0 + visible: !isMac && showMinimize + iconColor: control.textColor + color: hovered ? minimizeHoverColor : minimizeNormalColor + onClicked: minClickListener() + } + FluIconButton{ + id:btn_maximize + Layout.preferredWidth: 40 + Layout.preferredHeight: 30 + iconSource : d.isRestore ? FluentIcons.ChromeRestore : FluentIcons.ChromeMaximize + color: hovered ? maximizeHoverColor : maximizeNormalColor + Layout.alignment: Qt.AlignVCenter + visible: d.resizable && !isMac && showMaximize + radius: 0 + iconColor: control.textColor + text:d.isRestore?restoreText:maximizeText + iconSize: 11 + onClicked: maxClickListener() + } + FluIconButton{ + id:btn_close + iconSource : FluentIcons.ChromeClose + Layout.alignment: Qt.AlignVCenter + text:closeText + Layout.preferredWidth: 40 + Layout.preferredHeight: 30 + visible: !isMac && showClose + radius: 0 + iconSize: 10 + iconColor: hovered ? Qt.rgba(1,1,1,1) : control.textColor + color:hovered ? closeHoverColor : closeNormalColor + onClicked: closeClickListener() + } + } + + function minimizeButton(){ + return btn_minimize + } + + function maximizeButton(){ + return btn_maximize + } + + function closeButton(){ + return btn_close + } + + function darkButton(){ + return btn_dark + } + +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluArea.qml b/src/Qt5/imports/FluentUI/Controls/FluArea.qml new file mode 100644 index 00000000..3c826d39 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluArea.qml @@ -0,0 +1,27 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +Rectangle { + default property alias content: container.data + property int paddings : 0 + property int leftPadding : 0 + property int rightPadding : 0 + property int topPadding : 0 + property int bottomPadding : 0 + radius: 4 + color: FluTheme.dark ? Window.active ? Qt.rgba(38/255,44/255,54/255,1) : Qt.rgba(39/255,39/255,39/255,1) : Qt.rgba(251/255,251/255,253/255,1) + border.color: FluTheme.dark ? Window.active ? Qt.rgba(55/255,55/255,55/255,1):Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(226/255,229/255,234/255,1) + border.width: 1 + implicitHeight: height + implicitWidth: width + Item { + id: container + anchors.fill: parent + anchors.leftMargin: Math.max(paddings,leftPadding) + anchors.rightMargin: Math.max(paddings,rightPadding) + anchors.topMargin: Math.max(paddings,topPadding) + anchors.bottomMargin: Math.max(paddings,bottomPadding) + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluAutoSuggestBox.qml b/src/Qt5/imports/FluentUI/Controls/FluAutoSuggestBox.qml new file mode 100644 index 00000000..f5b4ea37 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluAutoSuggestBox.qml @@ -0,0 +1,135 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +FluTextBox{ + property var items:[] + property string emptyText: "没有找到结果" + property int autoSuggestBoxReplacement: FluentIcons.Search + signal itemClicked(var data) + signal handleClicked + id:control + Component.onCompleted: { + loadData() + } + Item{ + id:d + property bool flagVisible: true + property var window : Window.window + } + onActiveFocusChanged: { + if(!activeFocus){ + control_popup.visible = false + } + } + Popup{ + id:control_popup + y:control.height + focus: false + padding: 0 + enter: Transition { + NumberAnimation { + property: "opacity" + from:0 + to:1 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + contentItem: FluRectangle{ + radius: [4,4,4,4] + FluShadow{ + radius: 4 + } + color: FluTheme.dark ? Qt.rgba(51/255,48/255,48/255,1) : Qt.rgba(248/255,250/255,253/255,1) + ListView{ + id:list_view + anchors.fill: parent + clip: true + boundsBehavior: ListView.StopAtBounds + ScrollBar.vertical: FluScrollBar {} + header: Item{ + width: control.width + height: visible ? 38 : 0 + visible: list_view.count === 0 + FluText{ + text:emptyText + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: 10 + } + } + } + delegate:FluControl{ + id:item_control + height: 38 + width: control.width + onClicked:{ + handleClick(modelData) + } + background: Rectangle{ + FluFocusRectangle{ + visible: item_control.activeFocus + radius:4 + } + color: { + if(hovered){ + return FluTheme.dark ? Qt.rgba(63/255,60/255,61/255,1) : Qt.rgba(237/255,237/255,242/255,1) + } + return FluTheme.dark ? Qt.rgba(51/255,48/255,48/255,1) : Qt.rgba(0,0,0,0) + } + } + contentItem: FluText{ + text:modelData.title + leftPadding: 10 + rightPadding: 10 + verticalAlignment : Qt.AlignVCenter + } + } + } + } + background: Item{ + id:container + implicitWidth: control.width + implicitHeight: 38*Math.min(Math.max(list_view.count,1),8) + } + } + onTextChanged: { + loadData() + if(d.flagVisible){ + var pos = control.mapToItem(null, 0, 0) + if(d.window.height>pos.y+control.height+container.height){ + control_popup.y = control.height + } else if(pos.y>container.height){ + control_popup.y = -container.height + } else { + popup.y = d.window.height-(pos.y+container.height) + } + control_popup.visible = true + } + } + function handleClick(modelData){ + control_popup.visible = false + control.itemClicked(modelData) + updateText(modelData.title) + } + function updateText(text){ + d.flagVisible = false + control.text = text + d.flagVisible = true + } + function loadData(){ + var result = [] + if(items==null){ + list_view.model = result + return + } + items.map(function(item){ + if(item.title.indexOf(control.text)!==-1){ + result.push(item) + } + }) + list_view.model = result + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluBadge.qml b/src/Qt5/imports/FluentUI/Controls/FluBadge.qml new file mode 100644 index 00000000..65d17f14 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluBadge.qml @@ -0,0 +1,79 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Rectangle{ + property bool isDot: false + property bool showZero: false + property int count: 0 + property bool topRight: false + id:control + color:Qt.rgba(255/255,77/255,79/255,1) + width: { + if(isDot) + return 10 + if(count<10){ + return 20 + }else if(count<100){ + return 30 + } + return 40 + } + height: { + if(isDot) + return 10 + return 20 + } + radius: { + if(isDot) + return 5 + return 10 + } + border.width: 1 + border.color: Qt.rgba(1,1,1,1) + anchors{ + right: { + if(parent && topRight) + return parent.right + return undefined + } + top: { + if(parent && topRight) + return parent.top + return undefined + } + rightMargin: { + if(parent && topRight){ + if(isDot){ + return -2.5 + } + return -(control.width/2) + } + return 0 + } + topMargin: { + if(parent && topRight){ + if(isDot){ + return -2.5 + } + return -10 + } + return 0 + } + } + visible: { + if(showZero) + return true + return count!==0 + } + Text{ + anchors.centerIn: parent + color: Qt.rgba(1,1,1,1) + visible: !isDot + text:{ + if(count<100) + return count + return count+"+" + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluBreadcrumbBar.qml b/src/Qt5/imports/FluentUI/Controls/FluBreadcrumbBar.qml new file mode 100644 index 00000000..fba338ff --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluBreadcrumbBar.qml @@ -0,0 +1,92 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 + +Item { + property int textSize: 15 + property string separator: "/" + property var items: [] + property int spacing: 5 + signal clickItem(var model) + id:control + implicitWidth: 300 + height: 30 + onItemsChanged: { + list_model.clear() + list_model.append(items) + } + ListModel{ + id:list_model + } + ListView{ + id:list_view + width: parent.width + height: 30 + orientation: ListView.Horizontal + model: list_model + clip: true + spacing : control.spacing + boundsBehavior: ListView.StopAtBounds + remove: Transition { + NumberAnimation { + properties: "opacity" + from: 1 + to: 0 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + add: Transition { + NumberAnimation { + properties: "opacity" + from: 0 + to: 1 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + delegate: Item{ + height: item_layout.height + width: item_layout.width + RowLayout{ + id:item_layout + spacing: list_view.spacing + height: list_view.height + + FluText{ + text:model.title + Layout.alignment: Qt.AlignVCenter + color: { + if(item_mouse.pressed){ + return FluTheme.dark ? Qt.rgba(150/255,150/255,150/235,1) : Qt.rgba(134/255,134/255,134/235,1) + } + if(item_mouse.containsMouse){ + return FluTheme.dark ? Qt.rgba(204/255,204/255,204/235,1) : Qt.rgba(92/255,92/255,92/235,1) + } + return FluTheme.dark ? Qt.rgba(255/255,255/255,255/235,1) : Qt.rgba(26/255,26/255,26/235,1) + } + MouseArea{ + id:item_mouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + control.clickItem(model) + } + } + } + + FluText{ + text:control.separator + font.pixelSize: control.textSize + visible: list_view.count-1 !== index + Layout.alignment: Qt.AlignVCenter + } + } + } + } + function remove(index,count){ + list_model.remove(index,count) + } + function count(){ + return list_model.count + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluButton.qml b/src/Qt5/imports/FluentUI/Controls/FluButton.qml new file mode 100644 index 00000000..c946d0cd --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluButton.qml @@ -0,0 +1,63 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Button { + property bool disabled: false + property string contentDescription: "" + property color normalColor: FluTheme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(254/255,254/255,254/255,1) + property color hoverColor: FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1) + property color disableColor: FluTheme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(252/255,252/255,252/255,1) + property color textColor: { + if(FluTheme.dark){ + if(!enabled){ + return Qt.rgba(131/255,131/255,131/255,1) + } + if(pressed){ + return Qt.rgba(162/255,162/255,162/255,1) + } + return Qt.rgba(1,1,1,1) + }else{ + if(!enabled){ + return Qt.rgba(160/255,160/255,160/255,1) + } + if(pressed){ + return Qt.rgba(96/255,96/255,96/255,1) + } + return Qt.rgba(0,0,0,1) + } + } + Accessible.role: Accessible.Button + Accessible.name: control.text + Accessible.description: contentDescription + Accessible.onPressAction: control.clicked() + id: control + enabled: !disabled + horizontalPadding:12 + font:FluTextStyle.Body + focusPolicy:Qt.TabFocus + background: Rectangle{ + implicitWidth: 28 + implicitHeight: 28 + border.color: FluTheme.dark ? "#505050" : "#DFDFDF" + border.width: 1 + radius: 4 + color:{ + if(!enabled){ + return disableColor + } + return hovered ? hoverColor :normalColor + } + FluFocusRectangle{ + visible: control.activeFocus + radius:4 + } + } + contentItem: FluText { + text: control.text + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font: control.font + color: control.textColor + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluCalendarPicker.qml b/src/Qt5/imports/FluentUI/Controls/FluCalendarPicker.qml new file mode 100644 index 00000000..48026e8b --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluCalendarPicker.qml @@ -0,0 +1,114 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +Rectangle { + property color dividerColor: FluTheme.dark ? Qt.rgba(77/255,77/255,77/255,1) : Qt.rgba(239/255,239/255,239/255,1) + property color hoverColor: FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1) + property color normalColor: FluTheme.dark ? Qt.rgba(61/255,61/255,61/255,1) : Qt.rgba(254/255,254/255,254/255,1) + property string text: "请选择日期" + id:control + color: { + if(mouse_area.containsMouse){ + return hoverColor + } + return normalColor + } + height: 30 + width: 120 + radius: 4 + border.width: 1 + border.color: dividerColor + MouseArea{ + id:mouse_area + hoverEnabled: true + anchors.fill: parent + onClicked: { + popup.showPopup() + } + } + Item{ + id:d + property var window : Window.window + } + FluText{ + id:text_date + anchors{ + left: parent.left + right: parent.right + rightMargin: 30 + top: parent.top + bottom: parent.bottom + } + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text:control.text + } + FluIcon{ + iconSource: FluentIcons.Calendar + iconSize: 14 + iconColor: text_date.color + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: 12 + } + } + Menu{ + id:popup + height: container.height + width: container.width + modal: true + dim:false + enter: Transition { + reversible: true + NumberAnimation { + property: "opacity" + from:0 + to:1 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + exit:Transition { + NumberAnimation { + property: "opacity" + from:1 + to:0 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + contentItem: Item{ + clip: true + FluCalendarView{ + id:container + onDateClicked: + (date)=>{ + popup.close() + var year = date.getFullYear() + var month = date.getMonth() + var day = date.getDate() + text_date.text = year+"-"+(month+1)+"-"+day + } + } + } + background: Item{ + FluShadow{ + radius: 5 + } + } + function showPopup() { + var pos = control.mapToItem(null, 0, 0) + if(d.window.height>pos.y+control.height+container.height){ + popup.y = control.height + } else if(pos.y>container.height){ + popup.y = -container.height + } else { + popup.y = d.window.height-(pos.y+container.height) + } + popup.x = -(popup.width-control.width)/2 + popup.open() + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluCalendarView.qml b/src/Qt5/imports/FluentUI/Controls/FluCalendarView.qml new file mode 100644 index 00000000..cfc1fcf1 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluCalendarView.qml @@ -0,0 +1,435 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Item { + property int displayMode: FluCalendarViewType.Month + property var date: new Date() + property var currentDate : new Date() + property var toDay: new Date() + signal dateClicked(var date) + id:control + width: 280 + height: 325 + Component.onCompleted: { + updateMouth(date) + } + Component{ + id:com_week + Item{ + height: 40 + width: 40 + FluText{ + text:name + anchors.centerIn: parent + } + } + } + Component{ + id:com_year + Button{ + id:item_control + property bool isYear: control.toDay.getFullYear() === date.getFullYear() + height: 70 + width: 70 + onClicked:{ + control.date = date + displayMode = FluCalendarViewType.Year + updateYear(date) + } + background: Item{ + Rectangle{ + width: 60 + height: 60 + radius: 4 + anchors.centerIn: parent + color:{ + if(FluTheme.dark){ + if(item_control.hovered){ + return Qt.rgba(1,1,1,0.05) + } + return Qt.rgba(0,0,0,0) + }else{ + if(item_control.hovered){ + return Qt.rgba(0,0,0,0.05) + } + return Qt.rgba(0,0,0,0) + } + } + } + Rectangle{ + id:backgound_selected + anchors.centerIn: parent + width: 50 + height: 50 + radius: 25 + visible: isYear + color: FluTheme.primaryColor.dark + } + FluText{ + text:date.getFullYear() + anchors.centerIn: parent + color: { + if(isYear){ + return "#FFFFFF" + } + if(isDecade){ + return FluTheme.dark ? "#FFFFFF" : "#1A1A1A" + } + return Qt.rgba(150/255,150/255,150/255,1) + } + } + } + contentItem: Item{} + } + } + Component{ + id:com_month + Button{ + id:item_control + property bool isYear: control.date.getFullYear() === date.getFullYear() + property bool isMonth: control.toDay.getFullYear() === date.getFullYear() && control.toDay.getMonth() === date.getMonth() + height: 70 + width: 70 + onClicked:{ + control.date = date + displayMode = FluCalendarViewType.Month + updateMouth(date) + } + background: Item{ + Rectangle{ + width: 60 + height: 60 + radius: 4 + anchors.centerIn: parent + color:{ + if(FluTheme.dark){ + if(item_control.hovered){ + return Qt.rgba(1,1,1,0.05) + } + return Qt.rgba(0,0,0,0) + }else{ + if(item_control.hovered){ + return Qt.rgba(0,0,0,0.05) + } + return Qt.rgba(0,0,0,0) + } + } + } + Rectangle{ + id:backgound_selected + anchors.centerIn: parent + width: 50 + height: 50 + radius: 25 + visible: isMonth + color: FluTheme.primaryColor.dark + } + FluText{ + text:(date.getMonth()+1)+"月" + anchors.centerIn: parent + color: { + if(isMonth){ + return "#FFFFFF" + } + if(isYear){ + return FluTheme.dark ? "#FFFFFF" : "#1A1A1A" + } + return Qt.rgba(150/255,150/255,150/255,1) + } + } + } + contentItem: Item{} + } + } + Component{ + id:com_day + Button{ + id:item_control + property bool isMonth: control.date.getMonth() === date.getMonth() + property bool isDay: control.currentDate.getFullYear() === date.getFullYear() && control.currentDate.getMonth() === date.getMonth() && control.currentDate.getDate() === date.getDate() + property bool isToDay: control.toDay.getFullYear() === date.getFullYear() && control.toDay.getMonth() === date.getMonth() && control.toDay.getDate() === date.getDate() + height: 40 + width: 40 + onClicked: { + currentDate = date + control.dateClicked(date) + } + background: Item{ + Rectangle{ + width: 36 + height: 36 + radius: 4 + anchors.centerIn: parent + color:{ + if(FluTheme.dark){ + if(item_control.hovered){ + return Qt.rgba(1,1,1,0.05) + } + return Qt.rgba(0,0,0,0) + }else{ + if(item_control.hovered){ + return Qt.rgba(0,0,0,0.05) + } + return Qt.rgba(0,0,0,0) + } + } + } + Rectangle{ + id:backgound_today + anchors.centerIn: parent + width: 36 + height: 36 + radius: 18 + color:"#00000000" + visible: isDay + border.color: FluTheme.primaryColor.dark + border.width: 1 + } + Rectangle{ + id:backgound_selected + anchors.centerIn: parent + width: 30 + height: 30 + radius: 15 + visible: isToDay + color: FluTheme.primaryColor.dark + } + FluText{ + text:date.getDate() + anchors.centerIn: parent + color: { + if(isToDay){ + return "#FFFFFF" + } + if(isMonth){ + return FluTheme.dark ? "#FFFFFF" : "#1A1A1A" + } + return Qt.rgba(150/255,150/255,150/255,1) + } + } + } + contentItem: Item{} + } + } + FluArea{ + anchors.fill: parent + radius: 5 + FluShadow{ + radius: 5 + } + Rectangle{ + id:layout_divider + height: 1 + width: parent.width + color: FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(226/255,229/255,234/255,1) + anchors{ + top: parent.top + topMargin: 44 + } + } + Item{ + id:layout_top + anchors{ + left: parent.left + right: parent.right + top: parent.top + bottom: layout_divider.top + } + FluTextButton{ + id:title + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: 14 + } + disabled: displayMode === FluCalendarViewType.Decade + onClicked:{ + if(displayMode === FluCalendarViewType.Month){ + displayMode = FluCalendarViewType.Year + updateYear(date) + }else if(displayMode === FluCalendarViewType.Year){ + displayMode = FluCalendarViewType.Decade + updateDecade(date) + } + } + } + FluIconButton{ + id:icon_up + iconSource: FluentIcons.CaretUpSolid8 + iconSize: 10 + anchors{ + verticalCenter: parent.verticalCenter + right: icon_down.left + rightMargin: 14 + } + onClicked: { + var year = date.getFullYear() + var month = date.getMonth() + if(displayMode === FluCalendarViewType.Month){ + var lastMonthYear = year; + var lastMonthMonth = month - 1 + if (month === 0) { + lastMonthYear = year - 1 + lastMonthMonth = 11 + } + date = new Date(lastMonthYear,lastMonthMonth,1) + updateMouth(date) + }else if(displayMode === FluCalendarViewType.Year){ + date = new Date(year-1,month,1) + updateYear(date) + }else if(displayMode === FluCalendarViewType.Decade){ + date = new Date(Math.floor(year / 10) * 10-10,month,1) + updateDecade(date) + } + } + } + FluIconButton{ + id:icon_down + iconSource: FluentIcons.CaretDownSolid8 + iconSize: 10 + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: 8 + } + onClicked: { + var year = date.getFullYear() + var month = date.getMonth() + if(displayMode === FluCalendarViewType.Month){ + var nextMonthYear = year + var nextMonth = month + 1 + if (month === 11) { + nextMonthYear = year + 1 + nextMonth = 0 + } + date = new Date(nextMonthYear,nextMonth,1) + updateMouth(date) + }else if(displayMode === FluCalendarViewType.Year){ + date = new Date(year+1,month,1) + updateYear(date) + }else if(displayMode === FluCalendarViewType.Decade){ + date = new Date(Math.floor(year / 10) * 10+10,month,1) + updateDecade(date) + } + } + } + } + ListModel { + id:list_model + } + Item{ + id:layout_bottom + anchors{ + left: parent.left + right: parent.right + top: layout_divider.bottom + bottom: parent.bottom + } + GridView{ + model: list_model + anchors.fill: parent + cellHeight: displayMode === FluCalendarViewType.Month ? 40 : 70 + cellWidth: displayMode === FluCalendarViewType.Month ? 40 : 70 + clip: true + boundsBehavior:Flickable.StopAtBounds + delegate: Loader{ + property var modelData : model + property var name : model.name + property var date : model.date + property var isDecade: { + return model.isDecade + } + sourceComponent: { + if(model.type === 0){ + return com_week + } + if(model.type === 1){ + return com_day + } + if(model.type === 2){ + return com_month + } + if(model.type === 3){ + return com_year + } + return com_day + } + } + } + } + } + function createItemWeek(name){ + return {type:0,date:new Date(),name:name,isDecade:false} + } + function createItemDay(date){ + return {type:1,date:date,name:"",isDecade:false} + } + function createItemMonth(date){ + return {type:2,date:date,name:"",isDecade:false} + } + function createItemYear(date,isDecade){ + return {type:3,date:date,name:"",isDecade:isDecade} + } + function updateDecade(date){ + list_model.clear() + var year = date.getFullYear() + const decadeStart = Math.floor(year / 10) * 10; + for(var i = decadeStart ; i< decadeStart+10 ; i++){ + list_model.append(createItemYear(new Date(i,0,1),true)); + } + for(var j = decadeStart+10 ; j< decadeStart+16 ; j++){ + list_model.append(createItemYear(new Date(j,0,1),false)); + } + title.text = decadeStart+"-"+(decadeStart+10) + } + function updateYear(date){ + list_model.clear() + var year = date.getFullYear() + for(var i = 0 ; i< 12 ; i++){ + list_model.append(createItemMonth(new Date(year,i))); + } + for(var j = 0 ; j< 4 ; j++){ + list_model.append(createItemMonth(new Date(year+1,j))); + } + title.text = year+"年" + } + function updateMouth(date){ + list_model.clear() + list_model.append([createItemWeek("一"),createItemWeek("二"),createItemWeek("三"),createItemWeek("四"),createItemWeek("五"),createItemWeek("六"),createItemWeek("日")]) + var year = date.getFullYear() + var month = date.getMonth() + var day = date.getDate() + var firstDayOfMonth = new Date(year, month, 1) + var dayOfWeek = firstDayOfMonth.getDay() + var headerSize = (dayOfWeek===0?7:dayOfWeek)-1 + if(headerSize!==0){ + var lastMonthYear = year; + var lastMonthMonth = month - 1 + if (month === 0) { + lastMonthYear = year - 1 + lastMonthMonth = 11 + } + var lastMonthDays = new Date(lastMonthYear, lastMonthMonth+1, 0).getDate() + for (var i = headerSize-1; i >= 0; i--) { + list_model.append(createItemDay(new Date(lastMonthYear, lastMonthMonth,lastMonthDays-i))) + } + } + const lastDayOfMonth = new Date(year, month+1, 0).getDate() + for (let day = 1; day <= lastDayOfMonth; day++) { + list_model.append(createItemDay(new Date(year, month,day))) + } + var footerSize = 49-list_model.count + var nextMonthYear = year + var nextMonth = month + 1 + if (month === 11) { + nextMonthYear = year + 1 + nextMonth = 0 + } + const nextDayOfMonth = new Date(nextMonthYear, nextMonth+1, 0).getDate() + for (let j = 1; j <= footerSize; j++) { + list_model.append(createItemDay(new Date(nextMonthYear, nextMonth,j))) + } + title.text = year+"年"+(month+1)+"月" + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluCarousel.qml b/src/Qt5/imports/FluentUI/Controls/FluCarousel.qml new file mode 100644 index 00000000..0296c04c --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluCarousel.qml @@ -0,0 +1,201 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +FluItem { + property int loopTime: 2000 + property var model + property Component delegate + property bool showIndicator: true + property int indicatorGravity : Qt.AlignBottom | Qt.AlignHCenter + property int indicatorMarginLeft: 0 + property int indicatorMarginRight: 0 + property int indicatorMarginTop: 0 + property int indicatorMarginBottom: 20 + property int indicatorSpacing: 10 + property alias indicatorAnchors: layout_indicator.anchors + property Component indicatorDelegate : com_indicator + id:control + width: 400 + height: 300 + ListModel{ + id:content_model + } + QtObject{ + id:d + property bool flagXChanged: true + function setData(data){ + if(!data){ + return + } + content_model.clear() + content_model.append(data[data.length-1]) + content_model.append(data) + content_model.append(data[0]) + list_view.highlightMoveDuration = 0 + list_view.currentIndex = 1 + list_view.highlightMoveDuration = 250 + timer_run.restart() + } + } + ListView{ + id:list_view + anchors.fill: parent + snapMode: ListView.SnapOneItem + clip: true + boundsBehavior: ListView.StopAtBounds + model:content_model + maximumFlickVelocity: 4 * (list_view.orientation === Qt.Horizontal ? width : height) + preferredHighlightBegin: 0 + preferredHighlightEnd: 0 + highlightMoveDuration: 0 + Component.onCompleted: { + d.setData(control.model) + } + Connections{ + target: control + function onModelChanged(){ + d.setData(control.model) + } + } + orientation : ListView.Horizontal + delegate: Item{ + id:item_control + width: ListView.view.width + height: ListView.view.height + property int displayIndex: { + if(index === 0) + return content_model.count-3 + if(index === content_model.count-1) + return 0 + return index-1 + } + Loader{ + property int displayIndex : item_control.displayIndex + property var model: list_view.model.get(index) + anchors.fill: parent + sourceComponent: { + if(model){ + return control.delegate + } + return undefined + } + } + } + onMovementEnded:{ + currentIndex = list_view.contentX/list_view.width + if(currentIndex === 0){ + currentIndex = list_view.count-2 + }else if(currentIndex === list_view.count-1){ + currentIndex = 1 + } + d.flagXChanged = false + timer_run.restart() + } + onMovementStarted: { + d.flagXChanged = true + timer_run.stop() + } + onContentXChanged: { + if(d.flagXChanged){ + var maxX = Math.min(list_view.width*(currentIndex+1),list_view.count*list_view.width) + var minY = Math.max(0,(list_view.width*(currentIndex-1))) + if(contentX>=maxX){ + contentX = maxX + } + if(contentX<=minY){ + contentX = minY + } + } + } + } + Component{ + id:com_indicator + Rectangle{ + width: 8 + height: 8 + radius: 4 + FluShadow{ + radius: 4 + } + scale: checked ? 1.2 : 1 + color: checked ? FluTheme.primaryColor.dark : Qt.rgba(1,1,1,0.7) + border.width: mouse_item.containsMouse ? 1 : 0 + border.color: FluTheme.primaryColor.dark + MouseArea{ + id:mouse_item + hoverEnabled: true + anchors.fill: parent + onClicked: { + changedIndex(realIndex) + } + } + } + } + Row{ + id:layout_indicator + spacing: control.indicatorSpacing + anchors{ + horizontalCenter:(indicatorGravity & Qt.AlignHCenter) ? parent.horizontalCenter : undefined + verticalCenter: (indicatorGravity & Qt.AlignVCenter) ? parent.verticalCenter : undefined + bottom: (indicatorGravity & Qt.AlignBottom) ? parent.bottom : undefined + top: (indicatorGravity & Qt.AlignTop) ? parent.top : undefined + left: (indicatorGravity & Qt.AlignLeft) ? parent.left : undefined + right: (indicatorGravity & Qt.AlignRight) ? parent.right : undefined + bottomMargin: control.indicatorMarginBottom + leftMargin: control.indicatorMarginBottom + rightMargin: control.indicatorMarginBottom + topMargin: control.indicatorMarginBottom + } + visible: showIndicator + Repeater{ + id:repeater_indicator + model: list_view.count + Loader{ + property int displayIndex: { + if(index === 0) + return list_view.count-3 + if(index === list_view.count-1) + return 0 + return index-1 + } + property int realIndex: index + property bool checked: list_view.currentIndex === index + sourceComponent: { + if(index===0 || index===list_view.count-1) + return undefined + return control.indicatorDelegate + } + } + } + } + Timer{ + id:timer_anim + interval: 250 + onTriggered: { + list_view.highlightMoveDuration = 0 + if(list_view.currentIndex === list_view.count-1){ + list_view.currentIndex = 1 + } + } + } + Timer{ + id:timer_run + interval: control.loopTime + repeat: true + onTriggered: { + list_view.highlightMoveDuration = 250 + list_view.currentIndex = list_view.currentIndex+1 + timer_anim.start() + } + } + + function changedIndex(index){ + d.flagXChanged = true + timer_run.stop() + list_view.currentIndex = index + d.flagXChanged = false + timer_run.restart() + } + +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluChart.qml b/src/Qt5/imports/FluentUI/Controls/FluChart.qml new file mode 100644 index 00000000..7d15aa0b --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluChart.qml @@ -0,0 +1,122 @@ +import QtQuick 2.15 +import "./../JS/Chart.js" as Chart + +Canvas { + id: control + + property var jsChart: undefined + property string chartType + property var chartData + property var chartOptions + property double chartAnimationProgress: 0.1 + property int animationEasingType: Easing.InOutExpo + property double animationDuration: 500 + property var memorizedContext + property var memorizedData + property var memorizedOptions + property alias animationRunning: chartAnimator.running + signal animationFinished() + function animateToNewData() + { + chartAnimationProgress = 0.1; + jsChart.update(); + chartAnimator.restart(); + } + MouseArea { + id: event + anchors.fill: control + hoverEnabled: true + enabled: true + property var handler: undefined + property QtObject mouseEvent: QtObject { + property int left: 0 + property int top: 0 + property int x: 0 + property int y: 0 + property int clientX: 0 + property int clientY: 0 + property string type: "" + property var target + } + function submitEvent(mouse, type) { + mouseEvent.type = type + mouseEvent.clientX = mouse ? mouse.x : 0; + mouseEvent.clientY = mouse ? mouse.y : 0; + mouseEvent.x = mouse ? mouse.x : 0; + mouseEvent.y = mouse ? mouse.y : 0; + mouseEvent.left = 0; + mouseEvent.top = 0; + mouseEvent.target = control; + + if(handler) { + handler(mouseEvent); + } + + control.requestPaint(); + } + onClicked:(mouse)=> { + submitEvent(mouse, "click"); + } + onPositionChanged:(mouse)=> { + submitEvent(mouse, "mousemove"); + } + onExited: { + submitEvent(undefined, "mouseout"); + } + onEntered: { + submitEvent(undefined, "mouseenter"); + } + onPressed:(mouse)=> { + submitEvent(mouse, "mousedown"); + } + onReleased:(mouse)=> { + submitEvent(mouse, "mouseup"); + } + } + PropertyAnimation { + id: chartAnimator + target: control + property: "chartAnimationProgress" + alwaysRunToEnd: true + to: 1 + duration: control.animationDuration + easing.type: control.animationEasingType + onFinished: { + control.animationFinished(); + } + } + onChartAnimationProgressChanged: { + control.requestPaint(); + } + onPaint: { + if(control.getContext('2d') !== null && memorizedContext !== control.getContext('2d') || memorizedData !== control.chartData || memorizedOptions !== control.chartOptions) { + var ctx = control.getContext('2d'); + + jsChart = Chart.build(ctx, { + type: control.chartType, + data: control.chartData, + options: control.chartOptions + }); + + memorizedData = control.chartData ; + memorizedContext = control.getContext('2d'); + memorizedOptions = control.chartOptions; + + control.jsChart.bindEvents(function(newHandler) {event.handler = newHandler;}); + + chartAnimator.start(); + } + + jsChart.draw(chartAnimationProgress); + } + onWidthChanged: { + if(jsChart) { + jsChart.resize(); + } + } + onHeightChanged: { + if(jsChart) { + jsChart.resize(); + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluCheckBox.qml b/src/Qt5/imports/FluentUI/Controls/FluCheckBox.qml new file mode 100644 index 00000000..6ced72a0 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluCheckBox.qml @@ -0,0 +1,120 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 + +Button { + property bool disabled: false + property string contentDescription: "" + property color borderNormalColor: FluTheme.dark ? Qt.rgba(160/255,160/255,160/255,1) : Qt.rgba(136/255,136/255,136/255,1) + property color bordercheckedColor: FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark + property color borderHoverColor: FluTheme.dark ? Qt.rgba(167/255,167/255,167/255,1) : Qt.rgba(135/255,135/255,135/255,1) + property color borderDisableColor: FluTheme.dark ? Qt.rgba(82/255,82/255,82/255,1) : Qt.rgba(199/255,199/255,199/255,1) + property color borderPressedColor: FluTheme.dark ? Qt.rgba(90/255,90/255,90/255,1) : Qt.rgba(191/255,191/255,191/255,1) + property color normalColor: FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(247/255,247/255,247/255,1) + property color checkedColor: FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark + property color hoverColor: FluTheme.dark ? Qt.rgba(72/255,72/255,72/255,1) : Qt.rgba(236/255,236/255,236/255,1) + property color checkedHoverColor: FluTheme.dark ? Qt.darker(checkedColor,1.15) : Qt.lighter(checkedColor,1.15) + property color checkedPreesedColor: FluTheme.dark ? Qt.darker(checkedColor,1.3) : Qt.lighter(checkedColor,1.3) + property color checkedDisableColor: FluTheme.dark ? Qt.rgba(82/255,82/255,82/255,1) : Qt.rgba(199/255,199/255,199/255,1) + property color disableColor: FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(253/255,253/255,253/255,1) + property real size: 18 + property alias textColor: btn_text.textColor + property bool textRight: true + property real textSpacing: 6 + property var clickListener : function(){ + checked = !checked + } + id:control + enabled: !disabled + onClicked: clickListener() + onCheckableChanged: { + if(checkable){ + checkable = false + } + } + background: Item{ + FluFocusRectangle{ + radius: 4 + visible: control.activeFocus + } + } + horizontalPadding:2 + verticalPadding: 2 + Accessible.role: Accessible.Button + Accessible.name: control.text + Accessible.description: contentDescription + Accessible.onPressAction: control.clicked() + focusPolicy:Qt.TabFocus + contentItem: RowLayout{ + spacing: control.textSpacing + layoutDirection:control.textRight ? Qt.LeftToRight : Qt.RightToLeft + Rectangle{ + width: control.size + height: control.size + radius: 4 + border.color: { + if(!enabled){ + return borderDisableColor + } + if(checked){ + return bordercheckedColor + } + if(pressed){ + return borderPressedColor + } + if(hovered){ + return borderHoverColor + } + return borderNormalColor + } + border.width: 1 + color: { + if(checked){ + if(!enabled){ + return checkedDisableColor + } + if(pressed){ + return checkedPreesedColor + } + if(hovered){ + return checkedHoverColor + } + return checkedColor + } + if(!enabled){ + return disableColor + } + if(hovered){ + return hoverColor + } + return normalColor + } + Behavior on color { + enabled: FluTheme.enableAnimation + ColorAnimation{ + duration: 83 + } + } + FluIcon { + anchors.centerIn: parent + iconSource: FluentIcons.AcceptMedium + iconSize: 15 + visible: checked + iconColor: FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1) + Behavior on visible { + enabled: FluTheme.enableAnimation + NumberAnimation{ + duration: 83 + } + } + } + } + FluText{ + id:btn_text + text: control.text + Layout.alignment: Qt.AlignVCenter + visible: text !== "" + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluColorPicker.qml b/src/Qt5/imports/FluentUI/Controls/FluColorPicker.qml new file mode 100644 index 00000000..06289074 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluColorPicker.qml @@ -0,0 +1,93 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +Button{ + id:control + width: 36 + height: 36 + implicitWidth: width + implicitHeight: height + property alias colorValue: container.colorValue + background: + Rectangle{ + id:layout_color + radius: 5 + color:"#00000000" + border.color: { + if(hovered) + return FluTheme.primaryColor.light + return FluTheme.dark ? Qt.rgba(100/255,100/255,100/255,1) : Qt.rgba(200/255,200/255,200/255,1) + } + border.width: 1 + + Rectangle{ + anchors.fill: parent + anchors.margins: 4 + radius: 5 + color: container.colorValue + } + + } + Item{ + id: d + property var window : Window.window + } + contentItem: Item{} + onClicked: { + popup.showPopup() + } + Menu{ + id:popup + modal: true + dim:false + height: container.height + width: container.width + contentItem: Item{ + clip: true + FluColorView{ + id:container + } + } + background:Item{ + FluShadow{ + radius: 5 + } + } + enter: Transition { + reversible: true + NumberAnimation { + property: "opacity" + from:0 + to:1 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + + exit:Transition { + NumberAnimation { + property: "opacity" + from:1 + to:0 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + function showPopup() { + var pos = control.mapToItem(null, 0, 0) + if(d.window.height>pos.y+control.height+container.height){ + popup.y = control.height + } else if(pos.y>container.height){ + popup.y = -container.height + } else { + popup.y = d.window.height-(pos.y+container.height) + } + popup.x = -(popup.width-control.width)/2 + popup.open() + } + } + function setColor(color){ + container.setColor(color) + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluColorView.qml b/src/Qt5/imports/FluentUI/Controls/FluColorView.qml new file mode 100644 index 00000000..d3b023c2 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluColorView.qml @@ -0,0 +1,24 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 +import "ColorPicker" + +Item { + property alias colorValue: color_picker.colorValue + width: color_picker.width+10 + height: color_picker.height + FluArea{ + anchors.fill: parent + radius: 5 + FluShadow{ + radius: 5 + } + ColorPicker{ + id:color_picker + } + } + function setColor(color) { + color_picker.setColor(color) + } +} + diff --git a/src/Qt5/imports/FluentUI/Controls/FluComboBox.qml b/src/Qt5/imports/FluentUI/Controls/FluComboBox.qml new file mode 100644 index 00000000..bbe3fdfd --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluComboBox.qml @@ -0,0 +1,137 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 +import QtQuick.Templates 2.15 as T + +ComboBox { + id: control + signal commit(string text) + property bool disabled: false + property color normalColor: FluTheme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(254/255,254/255,254/255,1) + property color hoverColor: FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1) + property color disableColor: FluTheme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(252/255,252/255,252/255,1) + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding, + implicitIndicatorHeight + topPadding + bottomPadding) + font: FluTextStyle.Body + leftPadding: padding + (!control.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing) + rightPadding: padding + (control.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing) + enabled: !disabled + delegate: FluItemDelegate { + width: ListView.view.width + text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] : model[control.textRole]) : modelData + palette.text: control.palette.text + font: control.font + palette.highlightedText: control.palette.highlightedText + highlighted: control.highlightedIndex === index + hoverEnabled: control.hoverEnabled + } + focusPolicy:Qt.TabFocus + indicator: FluIcon { + x: control.mirrored ? control.padding : control.width - width - control.padding + y: control.topPadding + (control.availableHeight - height) / 2 + width: 28 + iconSource:FluentIcons.ChevronDown + iconSize: 15 + opacity: enabled ? 1 : 0.3 + } + contentItem: T.TextField { + property bool disabled: !control.editable + leftPadding: !control.mirrored ? 10 : control.editable && activeFocus ? 3 : 1 + rightPadding: control.mirrored ? 10 : control.editable && activeFocus ? 3 : 1 + topPadding: 6 - control.padding + bottomPadding: 6 - control.padding + renderType: FluTheme.nativeText ? Text.NativeRendering : Text.QtRendering + selectionColor: FluTools.colorAlpha(FluTheme.primaryColor.lightest,0.6) + selectedTextColor: color + text: control.editable ? control.editText : control.displayText + enabled: control.editable + autoScroll: control.editable + font:control.font + readOnly: control.down + color: FluTheme.dark ? Qt.rgba(255/255,255/255,255/255,1) : Qt.rgba(27/255,27/255,27/255,1) + inputMethodHints: control.inputMethodHints + validator: control.validator + selectByMouse: true + verticalAlignment: Text.AlignVCenter + leftInset:1 + topInset:1 + bottomInset:1 + rightInset:1 + background: FluTextBoxBackground{ + border.width: 0 + inputItem: contentItem + } + Component.onCompleted: { + forceActiveFocus() + } + Keys.onEnterPressed: (event)=> handleCommit(event) + Keys.onReturnPressed:(event)=> handleCommit(event) + function handleCommit(event){ + control.commit(control.editText) + } + } + + background: Rectangle { + implicitWidth: 140 + implicitHeight: 32 + border.color: FluTheme.dark ? "#505050" : "#DFDFDF" + border.width: 1 + visible: !control.flat || control.down + radius: 4 + FluFocusRectangle{ + visible: control.visualFocus + radius:4 + anchors.margins: -2 + } + color:{ + if(disabled){ + return disableColor + } + return hovered ? hoverColor :normalColor + } + } + + popup: T.Popup { + y: control.height + width: control.width + height: Math.min(contentItem.implicitHeight, control.Window.height - topMargin - bottomMargin) + topMargin: 6 + bottomMargin: 6 + contentItem: ListView { + clip: true + implicitHeight: contentHeight + model: control.delegateModel + currentIndex: control.highlightedIndex + highlightMoveDuration: 0 + T.ScrollIndicator.vertical: ScrollIndicator { } + } + enter: Transition { + NumberAnimation { + property: "opacity" + from:0 + to:1 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + exit:Transition { + NumberAnimation { + property: "opacity" + from:1 + to:0 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + background:Rectangle{ + color:FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(249/255,249/255,249/255,1) + border.color: FluTheme.dark ? Window.active ? Qt.rgba(55/255,55/255,55/255,1):Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(226/255,229/255,234/255,1) + border.width: 1 + radius: 5 + FluShadow{ + radius: 5 + } + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluContentDialog.qml b/src/Qt5/imports/FluentUI/Controls/FluContentDialog.qml new file mode 100644 index 00000000..44ab05ed --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluContentDialog.qml @@ -0,0 +1,129 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +FluPopup { + id: popup + property string title: "Title" + property string message: "Message" + property string neutralText: "Neutral" + property string negativeText: "Negative" + property string positiveText: "Positive" + property int delayTime: 100 + signal neutralClicked + signal negativeClicked + signal positiveClicked + property int buttonFlags: FluContentDialogType.NegativeButton | FluContentDialogType.PositiveButton + focus: true + implicitWidth: 400 + implicitHeight: text_title.height + text_message.height + layout_actions.height + Rectangle { + id:layout_content + anchors.fill: parent + color: 'transparent' + radius:5 + FluText{ + id:text_title + font: FluTextStyle.TitleLarge + text:title + topPadding: 20 + leftPadding: 20 + rightPadding: 20 + wrapMode: Text.WrapAnywhere + anchors{ + top:parent.top + left: parent.left + right: parent.right + } + } + FluText{ + id:text_message + font: FluTextStyle.Body + wrapMode: Text.WrapAnywhere + text:message + topPadding: 14 + leftPadding: 20 + rightPadding: 20 + bottomPadding: 14 + anchors{ + top:text_title.bottom + left: parent.left + right: parent.right + } + } + Rectangle{ + id:layout_actions + height: 68 + radius: 5 + color: FluTheme.dark ? Qt.rgba(32/255,32/255,32/255,1) : Qt.rgba(243/255,243/255,243/255,1) + anchors{ + top:text_message.bottom + left: parent.left + right: parent.right + } + RowLayout{ + anchors + { + centerIn: parent + margins: spacing + fill: parent + } + spacing: 15 + FluButton{ + id:neutral_btn + Layout.fillWidth: true + Layout.fillHeight: true + visible: popup.buttonFlags&FluContentDialogType.NeutralButton + text: neutralText + onClicked: { + popup.close() + timer_delay.targetFlags = FluContentDialogType.NeutralButton + timer_delay.restart() + } + } + FluButton{ + id:negative_btn + Layout.fillWidth: true + Layout.fillHeight: true + visible: popup.buttonFlags&FluContentDialogType.NegativeButton + text: negativeText + onClicked: { + popup.close() + timer_delay.targetFlags = FluContentDialogType.NegativeButton + timer_delay.restart() + } + } + FluFilledButton{ + id:positive_btn + Layout.fillWidth: true + Layout.fillHeight: true + visible: popup.buttonFlags&FluContentDialogType.PositiveButton + text: positiveText + onClicked: { + popup.close() + timer_delay.targetFlags = FluContentDialogType.PositiveButton + timer_delay.restart() + } + } + } + } + } + Timer{ + property int targetFlags + id:timer_delay + interval: popup.delayTime + onTriggered: { + if(targetFlags === FluContentDialogType.NegativeButton){ + negativeClicked() + } + if(targetFlags === FluContentDialogType.NeutralButton){ + neutralClicked() + } + if(targetFlags === FluContentDialogType.PositiveButton){ + positiveClicked() + } + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluContentPage.qml b/src/Qt5/imports/FluentUI/Controls/FluContentPage.qml new file mode 100644 index 00000000..05f48a68 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluContentPage.qml @@ -0,0 +1,60 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +FluPage { + property alias title: text_title.text + default property alias content: container.data + property int leftPadding: 10 + property int topPadding: 0 + property int rightPadding: 10 + property int bottomPadding: 10 + property alias color: status_view.color + property alias statusMode: status_view.statusMode + property alias loadingText: status_view.loadingText + property alias emptyText:status_view.emptyText + property alias errorText:status_view.errorText + property alias errorButtonText:status_view.errorButtonText + property alias loadingItem :status_view.loadingItem + property alias emptyItem : status_view.emptyItem + property alias errorItem :status_view.errorItem + signal errorClicked + + id:control + FluText{ + id:text_title + visible: text !== "" + height: visible ? contentHeight : 0 + font: FluTextStyle.Title + anchors{ + top: parent.top + topMargin: control.topPadding + left: parent.left + right: parent.right + leftMargin: control.leftPadding + rightMargin: control.rightPadding + } + } + FluStatusView{ + id:status_view + color: "#00000000" + statusMode: FluStatusViewType.Success + onErrorClicked: control.errorClicked() + anchors{ + left: parent.left + right: parent.right + top: text_title.bottom + bottom: parent.bottom + leftMargin: control.leftPadding + rightMargin: control.rightPadding + bottomMargin: control.bottomPadding + } + Item{ + clip: true + id:container + anchors.fill: parent + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluControl.qml b/src/Qt5/imports/FluentUI/Controls/FluControl.qml new file mode 100644 index 00000000..c8404b92 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluControl.qml @@ -0,0 +1,27 @@ +import QtQuick 2.15 +import FluentUI 1.0 +import QtQuick.Templates 2.15 as T + +T.Button { + id: control + property string contentDescription: "" + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + padding: 0 + horizontalPadding: 0 + spacing: 0 + contentItem: Item{} + focusPolicy:Qt.TabFocus + background: Item{ + FluFocusRectangle{ + visible: control.activeFocus + radius:8 + } + } + Accessible.role: Accessible.Button + Accessible.name: control.text + Accessible.description: contentDescription + Accessible.onPressAction: control.clicked() +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluCopyableText.qml b/src/Qt5/imports/FluentUI/Controls/FluCopyableText.qml new file mode 100644 index 00000000..b7e6861e --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluCopyableText.qml @@ -0,0 +1,35 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +TextEdit { + property color textColor: FluTheme.dark ? FluColors.White : FluColors.Grey220 + id:control + color: textColor + readOnly: true + activeFocusOnTab: false + activeFocusOnPress: false + renderType: FluTheme.nativeText ? Text.NativeRendering : Text.QtRendering + padding: 0 + leftPadding: 0 + rightPadding: 0 + topPadding: 0 + selectByMouse: true + selectedTextColor: color + bottomPadding: 0 + selectionColor: FluTools.colorAlpha(FluTheme.primaryColor.lightest,0.6) + font:FluTextStyle.Body + onSelectedTextChanged: { + control.forceActiveFocus() + } + MouseArea{ + anchors.fill: parent + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.RightButton + onClicked: control.echoMode !== TextInput.Password && menu.popup() + } + FluTextBoxMenu{ + id:menu + inputItem: control + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluDatePicker.qml b/src/Qt5/imports/FluentUI/Controls/FluDatePicker.qml new file mode 100644 index 00000000..d17e12ed --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluDatePicker.qml @@ -0,0 +1,401 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +Rectangle { + property color dividerColor: FluTheme.dark ? Qt.rgba(77/255,77/255,77/255,1) : Qt.rgba(239/255,239/255,239/255,1) + property color hoverColor: FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1) + property color normalColor: FluTheme.dark ? Qt.rgba(61/255,61/255,61/255,1) : Qt.rgba(254/255,254/255,254/255,1) + property bool showYear: true + property var current + id:control + color: { + if(mouse_area.containsMouse){ + return hoverColor + } + return normalColor + } + height: 30 + width: 300 + radius: 4 + border.width: 1 + border.color: dividerColor + Item{ + id:d + property var window: Window.window + property bool changeFlag: true + property var rowData: ["","",""] + visible: false + } + MouseArea{ + id:mouse_area + hoverEnabled: true + anchors.fill: parent + onClicked: { + popup.showPopup() + } + } + Rectangle{ + id:divider_1 + width: 1 + x: parent.width/3 + height: parent.height + color: dividerColor + visible: showYear + } + Rectangle{ + id:divider_2 + width: 1 + x: showYear ? parent.width*2/3 : parent.width/2 + height: parent.height + color: dividerColor + } + FluText{ + id:text_year + anchors{ + left: parent.left + right: divider_1.left + top: parent.top + bottom: parent.bottom + } + visible: showYear + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text:"年" + } + FluText{ + id:text_month + anchors{ + left: showYear ? divider_1.right : parent.left + right: divider_2.left + top: parent.top + bottom: parent.bottom + } + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text:"月" + } + FluText{ + id:text_day + anchors{ + left: divider_2.right + right: parent.right + top: parent.top + bottom: parent.bottom + } + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text:"日" + } + Menu{ + id:popup + modal: true + width: container.width + height: container.height + dim:false + enter: Transition { + reversible: true + NumberAnimation { + property: "opacity" + from:0 + to:1 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + exit:Transition { + NumberAnimation { + property: "opacity" + from:1 + to:0 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + background:Item{ + FluShadow{ + radius: 4 + } + } + contentItem: Item{ + clip: true + Rectangle{ + id:container + radius: 4 + width: 300 + height: 340 + color: FluTheme.dark ? Qt.rgba(51/255,48/255,48/255,1) : Qt.rgba(248/255,250/255,253/255,1) + MouseArea{ + anchors.fill: parent + } + FluShadow{ + radius: 4 + } + RowLayout{ + id:layout_content + spacing: 0 + width: parent.width + height: 300 + Component{ + id:list_delegate + Item{ + height:38 + width:getListView().width + function getListView(){ + if(type === 0) + return list_view_1 + if(type === 1) + return list_view_2 + if(type === 2) + return list_view_3 + } + Rectangle{ + anchors.fill: parent + anchors.topMargin: 2 + anchors.bottomMargin: 2 + anchors.leftMargin: 5 + anchors.rightMargin: 5 + color: { + if(getListView().currentIndex === position){ + if(FluTheme.dark){ + return item_mouse.containsMouse ? Qt.darker(FluTheme.primaryColor.lighter,1.1) : FluTheme.primaryColor.lighter + }else{ + return item_mouse.containsMouse ? Qt.lighter(FluTheme.primaryColor.dark,1.1): FluTheme.primaryColor.dark + } + } + if(item_mouse.containsMouse){ + return FluTheme.dark ? Qt.rgba(63/255,60/255,61/255,1) : Qt.rgba(237/255,237/255,242/255,1) + } + return FluTheme.dark ? Qt.rgba(51/255,48/255,48/255,1) : Qt.rgba(0,0,0,0) + } + radius: 3 + MouseArea{ + id:item_mouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + getListView().currentIndex = position + if(type === 0){ + text_year.text = model + list_view_2.model = generateMonthArray(1,12) + text_month.text = list_view_2.model[list_view_2.currentIndex] + + list_view_3.model = generateMonthDaysArray(list_view_1.model[list_view_1.currentIndex],list_view_2.model[list_view_2.currentIndex]) + text_day.text = list_view_3.model[list_view_3.currentIndex] + } + if(type === 1){ + text_month.text = model + list_view_3.model = generateMonthDaysArray(list_view_1.model[list_view_1.currentIndex],list_view_2.model[list_view_2.currentIndex]) + text_day.text = list_view_3.model[list_view_3.currentIndex] + + } + if(type === 2){ + text_day.text = model + } + } + } + FluText{ + text:model + color: { + if(getListView().currentIndex === position){ + if(FluTheme.dark){ + return Qt.rgba(0,0,0,1) + }else{ + return Qt.rgba(1,1,1,1) + } + }else{ + return FluTheme.dark ? "#FFFFFF" : "#1A1A1A" + } + } + anchors.centerIn: parent + } + } + } + } + ListView{ + id:list_view_1 + width: 100 + height: parent.height + boundsBehavior:Flickable.StopAtBounds + ScrollBar.vertical: FluScrollBar {} + model: generateYearArray(1924,2048) + clip: true + preferredHighlightBegin: 0 + preferredHighlightEnd: 0 + highlightMoveDuration: 0 + visible: showYear + delegate: Loader{ + property var model: modelData + property int type:0 + property int position:index + sourceComponent: list_delegate + } + } + Rectangle{ + width: 1 + height: parent.height + color: dividerColor + } + ListView{ + id:list_view_2 + width: showYear ? 100 : 150 + height: parent.height + clip: true + ScrollBar.vertical: FluScrollBar {} + preferredHighlightBegin: 0 + preferredHighlightEnd: 0 + highlightMoveDuration: 0 + boundsBehavior:Flickable.StopAtBounds + delegate: Loader{ + property var model: modelData + property int type:1 + property int position:index + sourceComponent: list_delegate + } + } + Rectangle{ + width: 1 + height: parent.height + color: dividerColor + } + ListView{ + id:list_view_3 + width: showYear ? 100 : 150 + height: parent.height + clip: true + preferredHighlightBegin: 0 + preferredHighlightEnd: 0 + highlightMoveDuration: 0 + ScrollBar.vertical: FluScrollBar {} + Layout.alignment: Qt.AlignVCenter + boundsBehavior:Flickable.StopAtBounds + delegate: Loader{ + property var model: modelData + property int type:2 + property int position:index + sourceComponent: list_delegate + } + } + } + Rectangle{ + width: parent.width + height: 1 + anchors.top: layout_content.bottom + color: dividerColor + } + Rectangle{ + id:layout_actions + height: 40 + radius: 5 + color: FluTheme.dark ? Qt.rgba(32/255,32/255,32/255,1) : Qt.rgba(243/255,243/255,243/255,1) + anchors{ + bottom:parent.bottom + left: parent.left + right: parent.right + } + Item { + id:divider + width: 1 + height: parent.height + anchors.centerIn: parent + } + FluButton{ + anchors{ + left: parent.left + leftMargin: 20 + rightMargin: 10 + right: divider.left + verticalCenter: parent.verticalCenter + } + text: "取消" + onClicked: { + popup.close() + } + } + FluFilledButton{ + anchors{ + right: parent.right + left: divider.right + rightMargin: 20 + leftMargin: 10 + verticalCenter: parent.verticalCenter + } + text: "确定" + onClicked: { + d.changeFlag = false + popup.close() + const year = text_year.text + const month = text_month.text + const day = text_day.text + const date = new Date() + date.setFullYear(parseInt(year)); + date.setMonth(parseInt(month) - 1); + date.setDate(parseInt(day)); + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + current = date + } + } + } + } + } + y:35 + function showPopup() { + d.changeFlag = true + d.rowData[0] = text_year.text + d.rowData[1] = text_month.text + d.rowData[2] = text_day.text + const now = new Date(); + var year = text_year.text === "年"? now.getFullYear() : Number(text_year.text); + var month = text_month.text === "月"? now.getMonth() + 1 : Number(text_month.text); + var day = text_day.text === "日" ? now.getDate() : Number(text_day.text); + list_view_1.currentIndex = list_view_1.model.indexOf(year) + text_year.text = year + list_view_2.model = generateMonthArray(1,12) + list_view_2.currentIndex = list_view_2.model.indexOf(month) + text_month.text = month + list_view_3.model = generateMonthDaysArray(year,month) + list_view_3.currentIndex = list_view_3.model.indexOf(day) + text_day.text = day + var pos = control.mapToItem(null, 0, 0) + if(d.window.height>pos.y+control.height+container.height){ + popup.y = control.height + } else if(pos.y>container.height){ + popup.y = -container.height + } else { + popup.y = d.window.height-(pos.y+container.height) + } + popup.open() + } + onClosed: { + if(d.changeFlag){ + text_year.text = d.rowData[0] + text_month.text = d.rowData[1] + text_day.text = d.rowData[2] + } + } + } + function generateYearArray(startYear, endYear) { + const yearArray = []; + for (let year = startYear; year <= endYear; year++) { + yearArray.push(year); + } + return yearArray; + } + function generateMonthArray(startMonth, endMonth) { + const monthArray = []; + for (let month = startMonth; month <= endMonth; month++) { + monthArray.push(month); + } + return monthArray; + } + function generateMonthDaysArray(year, month) { + const monthDaysArray = []; + const lastDayOfMonth = new Date(year, month, 0).getDate(); + for (let day = 1; day <= lastDayOfMonth; day++) { + monthDaysArray.push(day); + } + return monthDaysArray; + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluDivider.qml b/src/Qt5/imports/FluentUI/Controls/FluDivider.qml new file mode 100644 index 00000000..34fe3892 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluDivider.qml @@ -0,0 +1,18 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +Rectangle { + property real spacing + property alias separatorHeight:separator.height + + id:root + color:Qt.rgba(0,0,0,0) + height: spacing*2+separator.height + Rectangle{ + id:separator + color: FluTheme.dark ? Qt.rgba(80/255,80/255,80/255,1) : Qt.rgba(210/255,210/255,210/255,1) + width:parent.width + anchors.centerIn: parent + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluDropDownButton.qml b/src/Qt5/imports/FluentUI/Controls/FluDropDownButton.qml new file mode 100644 index 00000000..f3838809 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluDropDownButton.qml @@ -0,0 +1,94 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +Button { + property bool disabled: false + property string contentDescription: "" + property color normalColor: FluTheme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(254/255,254/255,254/255,1) + property color hoverColor: FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1) + property color disableColor: FluTheme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(252/255,252/255,252/255,1) + property color textColor: { + if(FluTheme.dark){ + if(!enabled){ + return Qt.rgba(131/255,131/255,131/255,1) + } + if(pressed){ + return Qt.rgba(162/255,162/255,162/255,1) + } + return Qt.rgba(1,1,1,1) + }else{ + if(!enabled){ + return Qt.rgba(160/255,160/255,160/255,1) + } + if(pressed){ + return Qt.rgba(96/255,96/255,96/255,1) + } + return Qt.rgba(0,0,0,1) + } + } + property var window : Window.window + default property alias contentData: menu.contentData + Accessible.role: Accessible.Button + Accessible.name: control.text + Accessible.description: contentDescription + Accessible.onPressAction: control.clicked() + id: control + rightPadding:35 + enabled: !disabled + focusPolicy:Qt.TabFocus + horizontalPadding:12 + background: Rectangle{ + implicitWidth: 28 + implicitHeight: 28 + border.color: FluTheme.dark ? "#505050" : "#DFDFDF" + border.width: 1 + radius: 4 + FluFocusRectangle{ + visible: control.activeFocus + radius:8 + } + color:{ + if(!enabled){ + return disableColor + } + return hovered ? hoverColor :normalColor + } + FluIcon{ + iconSource:FluentIcons.ChevronDown + iconSize: 15 + anchors{ + right: parent.right + rightMargin: 10 + verticalCenter: parent.verticalCenter + } + iconColor:title.color + } + } + contentItem: FluText { + id:title + text: control.text + verticalAlignment: Text.AlignVCenter + color: control.textColor + } + onClicked: { + if(menu.count !==0){ + var pos = control.mapToItem(null, 0, 0) + var containerHeight = menu.count*36 + if(window.height>pos.y+control.height+containerHeight){ + menu.y = control.height + }else if(pos.y>containerHeight){ + menu.y = -containerHeight + }else{ + menu.y = window.height-(pos.y+containerHeight) + } + menu.open() + } + } + FluMenu{ + id:menu + modal:true + width: control.width + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluExpander.qml b/src/Qt5/imports/FluentUI/Controls/FluExpander.qml new file mode 100644 index 00000000..d36b81e2 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluExpander.qml @@ -0,0 +1,122 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +Item { + property string headerText: "Titlte" + property bool expand: false + property int contentHeight : 300 + default property alias content: container.data + id:control + implicitHeight: Math.max((layout_header.height + container.height),layout_header.height) + implicitWidth: 400 + Rectangle{ + id:layout_header + width: parent.width + height: 45 + radius: 4 + color: FluTheme.dark ? Window.active ? Qt.rgba(38/255,44/255,54/255,1) : Qt.rgba(39/255,39/255,39/255,1) : Qt.rgba(251/255,251/255,253/255,1) + border.color: FluTheme.dark ? Window.active ? Qt.rgba(55/255,55/255,55/255,1) : Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(226/255,229/255,234/255,1) + MouseArea{ + id:control_mouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + expand = !expand + } + } + FluText{ + text: headerText + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: 15 + } + } + FluIconButton{ + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: 15 + } + color:{ + if(control_mouse.containsMouse || hovered){ + return FluTheme.dark ? Qt.rgba(73/255,73/255,73/255,1) : Qt.rgba(245/255,245/255,245/255,1) + } + return FluTheme.dark ? Qt.rgba(0,0,0,0) : Qt.rgba(0,0,0,0) + } + onClicked: { + expand = !expand + } + contentItem: FluIcon{ + rotation: expand?0:180 + iconSource:FluentIcons.ChevronUp + iconSize: 15 + Behavior on rotation { + enabled: FluTheme.enableAnimation + NumberAnimation{ + duration: 167 + easing.type: Easing.OutCubic + } + } + } + } + } + Item{ + anchors{ + top: layout_header.bottom + topMargin: -1 + left: layout_header.left + } + width: parent.width + clip: true + visible: contentHeight+container.y !== 0 + height: contentHeight+container.y + Rectangle{ + id:container + width: parent.width + height: parent.height + radius: 4 + color: FluTheme.dark ? Qt.rgba(39/255,39/255,39/255,1) : Qt.rgba(251/255,251/255,253/255,1) + border.color: FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(226/255,229/255,234/255,1) + y: -contentHeight + states: [ + State{ + name:"expand" + when: control.expand + PropertyChanges { + target: container + y:0 + } + }, + State{ + name:"collapsed" + when: !control.expand + PropertyChanges { + target: container + y:-contentHeight + } + } + ] + transitions: [ + Transition { + to:"expand" + NumberAnimation { + properties: "y" + duration: FluTheme.enableAnimation ? 167 : 0 + easing.type: Easing.OutCubic + } + }, + Transition { + to:"collapsed" + NumberAnimation { + properties: "y" + duration: FluTheme.enableAnimation ? 167 : 0 + easing.type: Easing.OutCubic + } + } + ] + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluFilledButton.qml b/src/Qt5/imports/FluentUI/Controls/FluFilledButton.qml new file mode 100644 index 00000000..3584e089 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluFilledButton.qml @@ -0,0 +1,56 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Button { + property bool disabled: false + property string contentDescription: "" + property color normalColor: FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark + property color hoverColor: FluTheme.dark ? Qt.darker(normalColor,1.1) : Qt.lighter(normalColor,1.1) + property color disableColor: FluTheme.dark ? Qt.rgba(82/255,82/255,82/255,1) : Qt.rgba(199/255,199/255,199/255,1) + property color pressedColor: FluTheme.dark ? Qt.darker(normalColor,1.2) : Qt.lighter(normalColor,1.2) + property color textColor: { + if(FluTheme.dark){ + if(!enabled){ + return Qt.rgba(173/255,173/255,173/255,1) + } + return Qt.rgba(0,0,0,1) + }else{ + return Qt.rgba(1,1,1,1) + } + } + Accessible.role: Accessible.Button + Accessible.name: control.text + Accessible.description: contentDescription + Accessible.onPressAction: control.clicked() + id: control + enabled: !disabled + focusPolicy:Qt.TabFocus + font:FluTextStyle.Body + horizontalPadding:12 + background: Rectangle{ + implicitWidth: 28 + implicitHeight: 28 + radius: 4 + FluFocusRectangle{ + visible: control.visualFocus + radius:4 + } + color:{ + if(!enabled){ + return disableColor + } + if(pressed){ + return pressedColor + } + return hovered ? hoverColor :normalColor + } + } + contentItem: FluText { + text: control.text + font: control.font + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: control.textColor + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluFlipView.qml b/src/Qt5/imports/FluentUI/Controls/FluFlipView.qml new file mode 100644 index 00000000..a8545f2f --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluFlipView.qml @@ -0,0 +1,106 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Item{ + property bool vertical: false + default property alias content : swipe.contentData + property alias currentIndex: swipe.currentIndex + id:control + width: 400 + height: 300 + implicitWidth: width + implicitHeight: height + QtObject{ + id:d + property bool flag: true + } + MouseArea{ + anchors.fill: parent + preventStealing: true + onWheel: + (wheel)=>{ + if(!d.flag) + return + if (wheel.angleDelta.y > 0){ + btn_start.clicked() + }else{ + btn_end.clicked() + } + d.flag = false + timer.restart() + } + } + Timer{ + id:timer + interval: 250 + onTriggered: { + d.flag = true + } + } + SwipeView { + id:swipe + clip: true + interactive: false + orientation:control.vertical ? Qt.Vertical : Qt.Horizontal + anchors.fill: parent + } + Button{ + id:btn_start + height: vertical ? 20 : 40 + width: vertical ? 40 : 20 + anchors{ + left: vertical ? undefined : parent.left + leftMargin: vertical ? undefined : 2 + verticalCenter: vertical ? undefined : parent.verticalCenter + horizontalCenter: !vertical ? undefined : parent.horizontalCenter + top: !vertical ? undefined :parent.top + topMargin: !vertical ? undefined :2 + } + background: Rectangle{ + radius: 4 + color:FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,0.97) : Qt.rgba(237/255,237/255,237/255,0.97) + } + contentItem:FluIcon{ + iconSource: vertical ? FluentIcons.CaretUpSolid8 : FluentIcons.CaretLeftSolid8 + width: 10 + height: 10 + iconSize: 10 + iconColor: btn_start.hovered ? FluColors.Grey220 : FluColors.Grey120 + anchors.centerIn: parent + } + visible: swipe.currentIndex !==0 + onClicked: { + swipe.currentIndex = Math.max(swipe.currentIndex - 1, 0) + } + } + Button{ + id:btn_end + height: vertical ? 20 : 40 + width: vertical ? 40 : 20 + anchors{ + right: vertical ? undefined : parent.right + rightMargin: vertical ? undefined : 2 + verticalCenter: vertical ? undefined : parent.verticalCenter + horizontalCenter: !vertical ? undefined : parent.horizontalCenter + bottom: !vertical ? undefined :parent.bottom + bottomMargin: !vertical ? undefined :2 + } + background: Rectangle{ + radius: 4 + color:FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,0.97) : Qt.rgba(237/255,237/255,237/255,0.97) + } + visible: swipe.currentIndex !== swipe.count - 1 + contentItem:FluIcon{ + iconSource: vertical ? FluentIcons.CaretDownSolid8 : FluentIcons.CaretRightSolid8 + width: 10 + height: 10 + iconSize: 10 + iconColor: btn_end.hovered ? FluColors.Grey220 : FluColors.Grey120 + anchors.centerIn: parent + } + onClicked: { + swipe.currentIndex = Math.min(swipe.currentIndex + 1,swipe.count-1) + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluFocusRectangle.qml b/src/Qt5/imports/FluentUI/Controls/FluFocusRectangle.qml new file mode 100644 index 00000000..82dc6903 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluFocusRectangle.qml @@ -0,0 +1,19 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Item { + property int radius: 4 + id:control + anchors.fill: parent + Rectangle{ + width: control.width + height: control.height + anchors.centerIn: parent + color: "#00000000" + border.width: 2 + radius: control.radius + border.color: FluTheme.dark ? Qt.rgba(1,1,1,1) : Qt.rgba(0,0,0,1) + z: 65535 + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluIcon.qml b/src/Qt5/imports/FluentUI/Controls/FluIcon.qml new file mode 100644 index 00000000..3a51b3f2 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluIcon.qml @@ -0,0 +1,19 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Text { + property int iconSource + property int iconSize: 20 + property color iconColor: FluTheme.dark ? "#FFFFFF" : "#000000" + id:control + font.family: "Segoe Fluent Icons" + font.pixelSize: iconSize + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: iconColor + text: (String.fromCharCode(iconSource).toString(16)) + FontLoader{ + source: "../Font/Segoe_Fluent_Icons.ttf" + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluIconButton.qml b/src/Qt5/imports/FluentUI/Controls/FluIconButton.qml new file mode 100644 index 00000000..e0e5efc2 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluIconButton.qml @@ -0,0 +1,140 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 + +Button { + display: Button.IconOnly + property int iconSize: 20 + property int iconSource + property bool disabled: false + property int radius:4 + property string contentDescription: "" + property color hoverColor: FluTheme.dark ? Qt.rgba(1,1,1,0.03) : Qt.rgba(0,0,0,0.03) + property color pressedColor: FluTheme.dark ? Qt.rgba(1,1,1,0.06) : Qt.rgba(0,0,0,0.06) + property color normalColor: FluTheme.dark ? Qt.rgba(0,0,0,0) : Qt.rgba(0,0,0,0) + property color disableColor: FluTheme.dark ? Qt.rgba(0,0,0,0) : Qt.rgba(0,0,0,0) + property Component iconDelegate: com_icon + property color color: { + if(!enabled){ + return disableColor + } + if(pressed){ + return pressedColor + } + return hovered ? hoverColor : normalColor + } + property color iconColor: { + if(FluTheme.dark){ + if(!enabled){ + return Qt.rgba(130/255,130/255,130/255,1) + } + return Qt.rgba(1,1,1,1) + }else{ + if(!enabled){ + return Qt.rgba(161/255,161/255,161/255,1) + } + return Qt.rgba(0,0,0,1) + } + } + Accessible.role: Accessible.Button + Accessible.name: control.text + Accessible.description: contentDescription + Accessible.onPressAction: control.clicked() + id:control + focusPolicy:Qt.TabFocus + padding: 0 + verticalPadding: 8 + horizontalPadding: 8 + enabled: !disabled + background: Rectangle{ + implicitWidth: 30 + implicitHeight: 30 + radius: control.radius + color:control.color + FluFocusRectangle{ + visible: control.activeFocus + } + } + Component{ + id:com_icon + FluIcon { + id:text_icon + font.pixelSize: iconSize + iconSize: control.iconSize + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + iconColor: control.iconColor + iconSource: control.iconSource + } + } + + Component{ + id:com_row + RowLayout{ + Loader{ + sourceComponent: iconDelegate + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + visible: display !== Button.TextOnly + } + FluText{ + text:control.text + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + visible: display !== Button.IconOnly + } + FluTooltip{ + id:tool_tip + visible: { + if(control.text === ""){ + return false + } + if(control.display !== Button.IconOnly){ + return false + } + return hovered + } + text:control.text + delay: 1000 + } + } + } + + Component{ + id:com_column + ColumnLayout{ + Loader{ + sourceComponent: iconDelegate + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + visible: display !== Button.TextOnly + } + FluText{ + text:control.text + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + visible: display !== Button.IconOnly + } + FluTooltip{ + id:tool_tip + visible: { + if(control.text === ""){ + return false + } + if(control.display !== Button.IconOnly){ + return false + } + return hovered + } + text:control.text + delay: 1000 + } + } + } + + contentItem:Loader{ + sourceComponent: { + if(display === Button.TextUnderIcon){ + return com_column + } + return com_row + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluImage.qml b/src/Qt5/imports/FluentUI/Controls/FluImage.qml new file mode 100644 index 00000000..78b02f47 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluImage.qml @@ -0,0 +1,48 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Image { + property string errorButtonText: "重新加载" + property var clickErrorListener : function(){ + image.source = "" + image.source = control.source + } + property Component errorItem : com_error + property Component loadingItem: com_loading + id: control + Loader{ + anchors.fill: parent + sourceComponent: { + if(control.status === Image.Loading){ + return com_loading + }else if(control.status == Image.Error){ + return com_error + }else{ + return undefined + } + } + } + Component{ + id:com_loading + Rectangle{ + color: FluTheme.dark ? Qt.rgba(1,1,1,0.03) : Qt.rgba(0,0,0,0.03) + FluProgressRing{ + anchors.centerIn: parent + visible: control.status === Image.Loading + } + } + } + Component{ + id:com_error + Rectangle{ + color: FluTheme.dark ? Qt.rgba(1,1,1,0.03) : Qt.rgba(0,0,0,0.03) + FluFilledButton{ + text: control.errorButtonText + anchors.centerIn: parent + visible: control.status === Image.Error + onClicked: clickErrorListener() + } + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluInfoBar.qml b/src/Qt5/imports/FluentUI/Controls/FluInfoBar.qml new file mode 100644 index 00000000..f24f627d --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluInfoBar.qml @@ -0,0 +1,210 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +FluObject { + property var root; + property int layoutY: 75 + id:control + FluObject{ + id:mcontrol + property string const_success: "success"; + property string const_info: "info"; + property string const_warning: "warning"; + property string const_error: "error"; + property int maxWidth: 300; + property var screenLayout: null; + function create(type,text,duration,moremsg){ + if(screenLayout){ + var last = screenLayout.getLastloader(); + if(last.type === type && last.text === text && moremsg === last.moremsg){ + last.restart(); + return; + } + } + initScreenLayout(); + contentComponent.createObject(screenLayout,{ + type:type, + text:text, + duration:duration, + moremsg:moremsg, + }); + } + function createCustom(itemcomponent,duration){ + initScreenLayout(); + if(itemcomponent){ + contentComponent.createObject(screenLayout,{itemcomponent:itemcomponent,duration:duration}); + } + } + function initScreenLayout(){ + if(screenLayout == null){ + screenLayout = screenlayoutComponent.createObject(root); + screenLayout.y = control.layoutY; + screenLayout.z = 100000; + } + } + Component{ + id:screenlayoutComponent + Column{ + spacing: 20 + width: parent.width + move: Transition { + NumberAnimation { + properties: "y" + easing.type: Easing.OutCubic + duration: FluTheme.enableAnimation ? 333 : 0 + } + } + onChildrenChanged: if(children.length === 0) destroy(); + function getLastloader(){ + if(children.length > 0){ + return children[children.length - 1]; + } + return null; + } + } + } + Component{ + id:contentComponent + Item{ + id:content; + property int duration: 1500 + property var itemcomponent + property string type + property string text + property string moremsg + width: parent.width; + height: loader.height; + function close(){ + content.destroy(); + } + function restart(){ + delayTimer.restart(); + } + Timer { + id:delayTimer + interval: duration; running: true; repeat: true + onTriggered: content.close(); + } + Loader{ + id:loader; + x:(parent.width - width) / 2; + property var _super: content; + scale: item ? 1 : 0; + asynchronous: true + Behavior on scale { + enabled: FluTheme.enableAnimation + NumberAnimation { + easing.type: Easing.OutCubic + duration: 167 + } + } + sourceComponent:itemcomponent ? itemcomponent : mcontrol.fluent_sytle; + } + } + } + property Component fluent_sytle: Rectangle{ + width: rowlayout.width + (_super.moremsg ? 25 : 80); + height: rowlayout.height + 20; + color: { + if(FluTheme.dark){ + switch(_super.type){ + case mcontrol.const_success: return Qt.rgba(57/255,61/255,27/255,1); + case mcontrol.const_warning: return Qt.rgba(67/255,53/255,25/255,1); + case mcontrol.const_info: return Qt.rgba(39/255,39/255,39/255,1); + case mcontrol.const_error: return Qt.rgba(68/255,39/255,38/255,1); + } + return Qt.rgba(255,255,255,1) + }else{ + switch(_super.type){ + case mcontrol.const_success: return "#dff6dd"; + case mcontrol.const_warning: return "#fff4ce"; + case mcontrol.const_info: return "#f4f4f4"; + case mcontrol.const_error: return "#fde7e9"; + } + return "#FFFFFF" + } + } + radius: 4 + border.width: 1 + border.color: { + if(FluTheme.dark){ + switch(_super.type){ + case mcontrol.const_success: return Qt.rgba(56/255,61/255,27/255,1); + case mcontrol.const_warning: return Qt.rgba(66/255,53/255,25/255,1); + case mcontrol.const_info: return Qt.rgba(38/255,39/255,39/255,1); + case mcontrol.const_error: return Qt.rgba(67/255,39/255,38/255,1); + } + return "#FFFFFF" + }else{ + switch(_super.type){ + case mcontrol.const_success: return "#d2e8d0"; + case mcontrol.const_warning: return "#f0e6c2"; + case mcontrol.const_info: return "#e6e6e6"; + case mcontrol.const_error: return "#eed9db"; + } + return "#FFFFFF" + } + } + Row{ + id:rowlayout + x:20; + y:(parent.height - height) / 2; + spacing: 10 + + FluIcon{ + iconSource:{ + switch(_super.type){ + case mcontrol.const_success: return FluentIcons.CompletedSolid; + case mcontrol.const_warning: return FluentIcons.InfoSolid; + case mcontrol.const_info: return FluentIcons.InfoSolid; + case mcontrol.const_error: return FluentIcons.StatusErrorFull; + }FluentIcons.StatusErrorFull + return FluentIcons.FA_info_circle + } + iconSize:20 + iconColor: { + if(FluTheme.dark){ + switch(_super.type){ + case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1); + case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/255,1); + case mcontrol.const_info: return FluTheme.primaryColor.lighter; + case mcontrol.const_error: return Qt.rgba(255/255,153/255,164/255,1); + } + return "#FFFFFF" + }else{ + switch(_super.type){ + case mcontrol.const_success: return "#0f7b0f"; + case mcontrol.const_warning: return "#9d5d00"; + case mcontrol.const_info: return "#0066b4"; + case mcontrol.const_error: return "#c42b1c"; + } + return "#FFFFFF" + } + } + } + + FluText{ + text:_super.text + wrapMode: Text.WrapAnywhere + width: Math.min(implicitWidth,mcontrol.maxWidth) + } + } + } + } + function showSuccess(text,duration=1000,moremsg){ + mcontrol.create(mcontrol.const_success,text,duration,moremsg ? moremsg : ""); + } + function showInfo(text,duration=1000,moremsg){ + mcontrol.create(mcontrol.const_info,text,duration,moremsg ? moremsg : ""); + } + function showWarning(text,duration=1000,moremsg){ + mcontrol.create(mcontrol.const_warning,text,duration,moremsg ? moremsg : ""); + } + function showError(text,duration=1000,moremsg){ + mcontrol.create(mcontrol.const_error,text,duration,moremsg ? moremsg : ""); + } + function showCustom(itemcomponent,duration=1000){ + mcontrol.createCustom(itemcomponent,duration); + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluItem.qml b/src/Qt5/imports/FluentUI/Controls/FluItem.qml new file mode 100644 index 00000000..51074233 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluItem.qml @@ -0,0 +1,57 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtGraphicalEffects 1.15 + +Item{ + property var radius:[0,0,0,0] + default property alias contentItem: container.data + id:control + Item{ + id:container + width: control.width + height: control.height + opacity: 0 + } + onWidthChanged: { + canvas.requestPaint() + } + onHeightChanged: { + canvas.requestPaint() + } + onRadiusChanged: { + canvas.requestPaint() + } + Canvas { + id: canvas + anchors.fill: parent + visible: false + onPaint: { + var ctx = getContext("2d"); + var x = 0; + var y = 0; + var w = control.width; + var h = control.height; + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x + radius[0], y); + ctx.lineTo(x + w - radius[1], y); + ctx.arcTo(x + w, y, x + w, y + radius[1], radius[1]); + ctx.lineTo(x + w, y + h - radius[2]); + ctx.arcTo(x + w, y + h, x + w - radius[2], y + h, radius[2]); + ctx.lineTo(x + radius[3], y + h); + ctx.arcTo(x, y + h, x, y + h - radius[3], radius[3]); + ctx.lineTo(x, y + radius[0]); + ctx.arcTo(x, y, x + radius[0], y, radius[0]); + ctx.closePath(); + ctx.fillStyle = control.color; + ctx.fill(); + ctx.restore(); + } + } + OpacityMask { + anchors.fill: container + source: container + maskSource: canvas + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluItemDelegate.qml b/src/Qt5/imports/FluentUI/Controls/FluItemDelegate.qml new file mode 100644 index 00000000..a852c591 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluItemDelegate.qml @@ -0,0 +1,38 @@ +import QtQuick 2.15 +import QtQuick.Templates 2.15 as T +import FluentUI 1.0 + +T.ItemDelegate { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding, + implicitIndicatorHeight + topPadding + bottomPadding) + + padding: 12 + spacing: 8 + + icon.width: 24 + icon.height: 24 + icon.color: control.palette.text + + contentItem: FluText { + text: control.text + font: control.font + } + + background: Rectangle { + implicitWidth: 100 + implicitHeight: 40 + color:{ + if(FluTheme.dark){ + return Qt.rgba(1,1,1,0.05) + }else{ + return Qt.rgba(0,0,0,0.05) + } + } + visible: control.down || control.highlighted || control.visualFocus + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluMenu.qml b/src/Qt5/imports/FluentUI/Controls/FluMenu.qml new file mode 100644 index 00000000..3485511d --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluMenu.qml @@ -0,0 +1,59 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Templates 2.15 as T +import FluentUI 1.0 + +T.Menu { + property bool enableAnimation: true + id: control + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + contentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding) + margins: 0 + overlap: 1 + spacing: 0 + delegate: FluMenuItem { } + enter: Transition { + NumberAnimation { + property: "opacity" + from:0 + to:1 + duration: FluTheme.enableAnimation && control.enableAnimation ? 83 : 0 + } + } + exit:Transition { + NumberAnimation { + property: "opacity" + from:1 + to:0 + duration: FluTheme.enableAnimation && control.enableAnimation ? 83 : 0 + } + } + contentItem: ListView { + implicitHeight: contentHeight + model: control.contentModel + interactive: Window.window + ? contentHeight + control.topPadding + control.bottomPadding > Window.window.height + : false + clip: true + currentIndex: control.currentIndex + ScrollIndicator.vertical: ScrollIndicator {} + } + background: Rectangle { + implicitWidth: 150 + implicitHeight: 36 + color:FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(240/255,240/255,240/255,1) + border.color: FluTheme.dark ? Window.active ? Qt.rgba(55/255,55/255,55/255,1):Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(226/255,229/255,234/255,1) + border.width: 1 + radius: 5 + FluShadow{} + } + T.Overlay.modal: Rectangle { + color: FluTools.colorAlpha(control.palette.shadow, 0.5) + } + T.Overlay.modeless: Rectangle { + color: FluTools.colorAlpha(control.palette.shadow, 0.12) + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluMenuBar.qml b/src/Qt5/imports/FluentUI/Controls/FluMenuBar.qml new file mode 100644 index 00000000..0151af6b --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluMenuBar.qml @@ -0,0 +1,20 @@ +import QtQuick 2.15 +import QtQuick.Templates 2.15 as T + +T.MenuBar { + id: control + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + contentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + contentHeight + topPadding + bottomPadding) + delegate: FluMenuBarItem { } + contentItem: Row { + spacing: control.spacing + Repeater { + model: control.contentModel + } + } + background: Item { + implicitHeight: 30 + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluMenuBarItem.qml b/src/Qt5/imports/FluentUI/Controls/FluMenuBarItem.qml new file mode 100644 index 00000000..bc17c4ca --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluMenuBarItem.qml @@ -0,0 +1,69 @@ +import QtQuick 2.15 +import QtQuick.Templates 2.15 as T +import FluentUI 1.0 + +T.MenuBarItem { + property bool disabled: false + property color textColor: { + if(FluTheme.dark){ + if(disabled){ + return Qt.rgba(131/255,131/255,131/255,1) + } + if(pressed){ + return Qt.rgba(162/255,162/255,162/255,1) + } + return Qt.rgba(1,1,1,1) + }else{ + if(disabled){ + return Qt.rgba(160/255,160/255,160/255,1) + } + if(pressed){ + return Qt.rgba(96/255,96/255,96/255,1) + } + return Qt.rgba(0,0,0,1) + } + } + id: control + enabled: !disabled + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding, + implicitIndicatorHeight + topPadding + bottomPadding) + spacing: 6 + padding: 6 + leftPadding: 12 + rightPadding: 16 + icon.width: 24 + icon.height: 24 + icon.color: control.palette.buttonText + contentItem: FluText { + verticalAlignment: Text.AlignVCenter + text: control.text + color:control.textColor + } + background: Rectangle { + implicitWidth: 30 + implicitHeight: 30 + radius: 3 + color: { + if(FluTheme.dark){ + if(control.highlighted){ + return Qt.rgba(1,1,1,0.06) + } + if(control.hovered){ + return Qt.rgba(1,1,1,0.03) + } + return Qt.rgba(0,0,0,0) + }else{ + if(control.highlighted){ + return Qt.rgba(0,0,0,0.06) + } + if(control.hovered){ + return Qt.rgba(0,0,0,0.03) + } + return Qt.rgba(0,0,0,0) + } + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluMenuItem.qml b/src/Qt5/imports/FluentUI/Controls/FluMenuItem.qml new file mode 100644 index 00000000..58046db9 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluMenuItem.qml @@ -0,0 +1,115 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Templates 2.15 as T +import FluentUI 1.0 + +T.MenuItem { + property Component iconDelegate : com_icon + property int iconSpacing: 5 + property int iconSource + property int iconSize: 16 + property color textColor: { + if(FluTheme.dark){ + if(!enabled){ + return Qt.rgba(131/255,131/255,131/255,1) + } + if(pressed){ + return Qt.rgba(162/255,162/255,162/255,1) + } + return Qt.rgba(1,1,1,1) + }else{ + if(!enabled){ + return Qt.rgba(160/255,160/255,160/255,1) + } + if(pressed){ + return Qt.rgba(96/255,96/255,96/255,1) + } + return Qt.rgba(0,0,0,1) + } + } + id: control + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding, + implicitIndicatorHeight + topPadding + bottomPadding) + padding: 6 + spacing: 6 + icon.width: 24 + icon.height: 24 + icon.color: control.palette.windowText + height: visible ? implicitHeight : 0 + Component{ + id:com_icon + FluIcon{ + id:content_icon + iconSize: control.iconSize + iconSource:control.iconSource + } + } + contentItem: Item{ + Row{ + spacing: control.iconSpacing + readonly property real arrowPadding: control.subMenu && control.arrow ? control.arrow.width + control.spacing : 0 + readonly property real indicatorPadding: control.checkable && control.indicator ? control.indicator.width + control.spacing : 0 + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: (!control.mirrored ? indicatorPadding : arrowPadding)+5 + right: parent.right + rightMargin: (control.mirrored ? indicatorPadding : arrowPadding)+5 + } + Loader{ + id:loader_icon + sourceComponent: iconDelegate + anchors.verticalCenter: parent.verticalCenter + visible: status === Loader.Ready + } + FluText { + id:content_text + text: control.text + color: control.textColor + anchors.verticalCenter: parent.verticalCenter + } + } + } + indicator: FluIcon { + x: control.mirrored ? control.width - width - control.rightPadding : control.leftPadding + y: control.topPadding + (control.availableHeight - height) / 2 + visible: control.checked + iconSource: FluentIcons.CheckMark + } + arrow: FluIcon { + x: control.mirrored ? control.leftPadding : control.width - width - control.rightPadding + y: control.topPadding + (control.availableHeight - height) / 2 + visible: control.subMenu + iconSource: FluentIcons.ChevronRightMed + } + background: Item { + implicitWidth: 150 + implicitHeight: 36 + x: 1 + y: 1 + width: control.width - 2 + height: control.height - 2 + Rectangle{ + anchors.fill: parent + anchors.margins: 3 + radius: 4 + color:{ + if(FluTheme.dark){ + if(control.highlighted){ + return Qt.rgba(1,1,1,0.06) + } + return Qt.rgba(0,0,0,0) + }else{ + if(control.highlighted){ + return Qt.rgba(0,0,0,0.06) + } + return Qt.rgba(0,0,0,0) + } + } + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluMenuSeparator.qml b/src/Qt5/imports/FluentUI/Controls/FluMenuSeparator.qml new file mode 100644 index 00000000..426af36e --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluMenuSeparator.qml @@ -0,0 +1,18 @@ +import QtQuick 2.15 +import QtQuick.Templates 2.15 as T +import FluentUI 1.0 + +T.MenuSeparator { + id: control + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + padding: 0 + verticalPadding: 0 + contentItem: Rectangle { + implicitWidth: 188 + implicitHeight: 1 + color: FluTheme.dark ? Qt.rgba(60/255,60/255,60/255,1) : Qt.rgba(210/255,210/255,210/255,1) + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluMultilineTextBox.qml b/src/Qt5/imports/FluentUI/Controls/FluMultilineTextBox.qml new file mode 100644 index 00000000..5d9bc200 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluMultilineTextBox.qml @@ -0,0 +1,65 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +TextArea{ + signal commit(string text) + property bool disabled: false + property color normalColor: FluTheme.dark ? Qt.rgba(255/255,255/255,255/255,1) : Qt.rgba(27/255,27/255,27/255,1) + property color disableColor: FluTheme.dark ? Qt.rgba(131/255,131/255,131/255,1) : Qt.rgba(160/255,160/255,160/255,1) + property color placeholderNormalColor: FluTheme.dark ? Qt.rgba(210/255,210/255,210/255,1) : Qt.rgba(96/255,96/255,96/255,1) + property color placeholderFocusColor: FluTheme.dark ? Qt.rgba(152/255,152/255,152/255,1) : Qt.rgba(141/255,141/255,141/255,1) + property color placeholderDisableColor: FluTheme.dark ? Qt.rgba(131/255,131/255,131/255,1) : Qt.rgba(160/255,160/255,160/255,1) + id:control + enabled: !disabled + color: { + if(!enabled){ + return disableColor + } + return normalColor + } + font:FluTextStyle.Body + wrapMode: Text.WrapAnywhere + padding: 8 + leftPadding: padding+2 + renderType: FluTheme.nativeText ? Text.NativeRendering : Text.QtRendering + selectedTextColor: color + selectionColor: FluTools.colorAlpha(FluTheme.primaryColor.lightest,0.6) + placeholderTextColor: { + if(!enabled){ + return placeholderDisableColor + } + if(focus){ + return placeholderFocusColor + } + return placeholderNormalColor + } + selectByMouse: true + width: background.implicitWidth + background: FluTextBoxBackground{ + inputItem: control + implicitWidth: 240 + } + Keys.onEnterPressed: (event)=> d.handleCommit(event) + Keys.onReturnPressed:(event)=> d.handleCommit(event) + QtObject{ + id:d + function handleCommit(event){ + if(event.modifiers & Qt.ControlModifier){ + insert(control.cursorPosition, "\n") + return + } + control.commit(control.text) + } + } + MouseArea{ + anchors.fill: parent + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.RightButton + onClicked: control.echoMode !== TextInput.Password && menu.popup() + } + FluTextBoxMenu{ + id:menu + inputItem: control + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml b/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml new file mode 100644 index 00000000..a2ef685a --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml @@ -0,0 +1,1222 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 + +Item { + property url logo + property string title: "" + property FluObject items + property FluObject footerItems + property int displayMode: FluNavigationViewType.Auto + property Component autoSuggestBox + property Component actionItem + property int topPadding: 0 + property int navWidth: 300 + property int pageMode: FluNavigationViewType.Stack + property FluMenu navItemRightMenu + property FluMenu navItemExpanderRightMenu + signal logoClicked + id:control + Item{ + id:d + property bool animDisabled:false + property var stackItems: [] + property int displayMode: control.displayMode + property bool enableNavigationPanel: false + property bool isCompact: d.displayMode === FluNavigationViewType.Compact + property bool isMinimal: d.displayMode === FluNavigationViewType.Minimal + property bool isCompactAndPanel: d.displayMode === FluNavigationViewType.Compact && d.enableNavigationPanel + property bool isCompactAndNotPanel:d.displayMode === FluNavigationViewType.Compact && !d.enableNavigationPanel + property bool isMinimalAndPanel: d.displayMode === FluNavigationViewType.Minimal && d.enableNavigationPanel + onIsCompactAndNotPanelChanged: { + collapseAll() + } + function handleItems(){ + var _idx = 0 + var data = [] + if(items){ + for(var i=0;i children + id:control +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluPage.qml b/src/Qt5/imports/FluentUI/Controls/FluPage.qml new file mode 100644 index 00000000..85b95996 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluPage.qml @@ -0,0 +1,34 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +Item { + property int launchMode: FluPageType.SingleTop + property bool animDisabled: false + property string url : "" + id: control + opacity: visible + visible: false + StackView.onRemoved: destroy() + Behavior on opacity{ + enabled: !animDisabled && FluTheme.enableAnimation + NumberAnimation{ + duration: 167 + } + } + transform: Translate { + y: control.visible ? 0 : 80 + Behavior on y{ + enabled: !animDisabled && FluTheme.enableAnimation + NumberAnimation{ + duration: 167 + easing.type: Easing.OutCubic + } + } + } + Component.onCompleted: { + visible = true + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluPagination.qml b/src/Qt5/imports/FluentUI/Controls/FluPagination.qml new file mode 100644 index 00000000..b7391ee6 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluPagination.qml @@ -0,0 +1,100 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 + +Item { + signal requestPage(int page,int count) + property string previousText: "<上一页" + property string nextText: "下一页>" + property int pageCurrent: 0 + property int itemCount: 0 + property int pageButtonCount: 5 + property int pageCount: itemCount>0?Math.ceil(itemCount/__itemPerPage):0 + property int __itemPerPage: 10 + property int __pageButtonHalf: Math.floor(pageButtonCount/2)+1 + id: control + implicitHeight: 40 + implicitWidth: content.width + Row{ + id: content + height: control.height + spacing: 10 + padding: 10 + FluToggleButton{ + visible: control.pageCount>1 + disabled: control.pageCurrent<=1 + text:control.previousText + clickListener:function() { + control.calcNewPage(control.pageCurrent-1); + } + } + Row{ + spacing: 5 + FluToggleButton{ + property int pageNumber:1 + visible: control.pageCount>0 + checked: pageNumber === control.pageCurrent + text:String(pageNumber) + clickListener:function() { + control.calcNewPage(pageNumber); + } + } + FluText{ + visible: (control.pageCount>control.pageButtonCount&& + control.pageCurrent>control.__pageButtonHalf) + text: "..." + } + Repeater{ + id: button_repeator + model: (control.pageCount<2)?0:(control.pageCount>=control.pageButtonCount)?(control.pageButtonCount-2):(control.pageCount-2) + delegate:FluToggleButton{ + property int pageNumber: { + return (control.pageCurrent<=control.__pageButtonHalf) + ?(2+index) + :(control.pageCount-control.pageCurrent<=control.pageButtonCount-control.__pageButtonHalf) + ?(control.pageCount-button_repeator.count+index) + :(control.pageCurrent+2+index-control.__pageButtonHalf) + } + text:String(pageNumber) + checked: pageNumber === control.pageCurrent + clickListener:function(){ + control.calcNewPage(pageNumber); + } + } + } + FluText{ + visible: (control.pageCount>control.pageButtonCount&& + control.pageCount-control.pageCurrent>control.pageButtonCount-control.__pageButtonHalf) + text: "..." + } + FluToggleButton{ + property int pageNumber:control.pageCount + visible: control.pageCount>1 + checked: pageNumber === control.pageCurrent + text:String(pageNumber) + clickListener:function(){ + control.calcNewPage(pageNumber); + } + } + } + FluToggleButton{ + visible: control.pageCount>1 + disabled: control.pageCurrent>=control.pageCount + text:control.nextText + clickListener:function() { + control.calcNewPage(control.pageCurrent+1); + } + } + } + function calcNewPage(page) + { + if(!page) + return + let page_num=Number(page) + if(page_num<1||page_num>control.pageCount||page_num===control.pageCurrent) + return + control.pageCurrent=page_num + control.requestPage(page_num,control.__itemPerPage) + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluPaneItem.qml b/src/Qt5/imports/FluentUI/Controls/FluPaneItem.qml new file mode 100644 index 00000000..4e3e79c1 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluPaneItem.qml @@ -0,0 +1,25 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +QtObject { + readonly property string key : FluTools.uuid() + property int _idx + property var _ext + property string title + property int order : 0 + property int icon + property Component cusIcon + property Component infoBadge + property bool recentlyAdded: false + property bool recentlyUpdated: false + property string desc + property var image + property var parent + property int count: 0 + signal tap + property var tapFunc + property Component menuDelegate + property Component editDelegate + property bool showEdit +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluPaneItemEmpty.qml b/src/Qt5/imports/FluentUI/Controls/FluPaneItemEmpty.qml new file mode 100644 index 00000000..92bb64c6 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluPaneItemEmpty.qml @@ -0,0 +1,10 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +QtObject { + readonly property string key : FluTools.uuid() + property int _idx + property var _ext + property var parent +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluPaneItemExpander.qml b/src/Qt5/imports/FluentUI/Controls/FluPaneItemExpander.qml new file mode 100644 index 00000000..4a700085 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluPaneItemExpander.qml @@ -0,0 +1,16 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +FluObject { + readonly property string key : FluTools.uuid() + property int _idx + property string title + property var icon + property Component cusIcon + property bool isExpand: false + property var parent + property Component menuDelegate + property Component editDelegate + property bool showEdit +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluPaneItemHeader.qml b/src/Qt5/imports/FluentUI/Controls/FluPaneItemHeader.qml new file mode 100644 index 00000000..2591cf18 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluPaneItemHeader.qml @@ -0,0 +1,10 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +QtObject { + readonly property string key : FluTools.uuid() + property int _idx + property string title + property var parent +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluPaneItemSeparator.qml b/src/Qt5/imports/FluentUI/Controls/FluPaneItemSeparator.qml new file mode 100644 index 00000000..7fa94f9a --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluPaneItemSeparator.qml @@ -0,0 +1,11 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +QtObject { + readonly property string key : FluTools.uuid() + property int _idx + property var parent + property real spacing + property int size:1 +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluPasswordBox.qml b/src/Qt5/imports/FluentUI/Controls/FluPasswordBox.qml new file mode 100644 index 00000000..1d5ed6e5 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluPasswordBox.qml @@ -0,0 +1,84 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +TextField{ + signal commit(string text) + property bool disabled: false + property int iconSource: 0 + property color normalColor: FluTheme.dark ? Qt.rgba(255/255,255/255,255/255,1) : Qt.rgba(27/255,27/255,27/255,1) + property color disableColor: FluTheme.dark ? Qt.rgba(131/255,131/255,131/255,1) : Qt.rgba(160/255,160/255,160/255,1) + property color placeholderNormalColor: FluTheme.dark ? Qt.rgba(210/255,210/255,210/255,1) : Qt.rgba(96/255,96/255,96/255,1) + property color placeholderFocusColor: FluTheme.dark ? Qt.rgba(152/255,152/255,152/255,1) : Qt.rgba(141/255,141/255,141/255,1) + property color placeholderDisableColor: FluTheme.dark ? Qt.rgba(131/255,131/255,131/255,1) : Qt.rgba(160/255,160/255,160/255,1) + id:control + enabled: !disabled + color: { + if(!enabled){ + return disableColor + } + return normalColor + } + font:FluTextStyle.Body + padding: 8 + leftPadding: padding+2 + echoMode:btn_reveal.pressed ? TextField.Normal : TextField.Password + renderType: FluTheme.nativeText ? Text.NativeRendering : Text.QtRendering + selectionColor: FluTools.colorAlpha(FluTheme.primaryColor.lightest,0.6) + selectedTextColor: color + placeholderTextColor: { + if(!enabled){ + return placeholderDisableColor + } + if(focus){ + return placeholderFocusColor + } + return placeholderNormalColor + } + selectByMouse: true + rightPadding: icon_end.visible ? 50 : 30 + background: FluTextBoxBackground{ + inputItem: control + implicitWidth: 240 + FluIcon{ + id:icon_end + iconSource: control.iconSource + iconSize: 15 + opacity: 0.5 + visible: control.iconSource != 0 + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: 5 + } + } + } + Keys.onEnterPressed: (event)=> d.handleCommit(event) + Keys.onReturnPressed:(event)=> d.handleCommit(event) + QtObject{ + id:d + function handleCommit(event){ + control.commit(control.text) + } + } + FluIconButton{ + id:btn_reveal + iconSource:FluentIcons.RevealPasswordMedium + iconSize: 10 + width: 20 + height: 20 + verticalPadding: 0 + horizontalPadding: 0 + opacity: 0.5 + visible: control.text !== "" + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: icon_end.visible ? 25 : 5 + } + } + FluTextBoxMenu{ + id:menu + inputItem: control + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluPivot.qml b/src/Qt5/imports/FluentUI/Controls/FluPivot.qml new file mode 100644 index 00000000..8ddbc7dc --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluPivot.qml @@ -0,0 +1,92 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Item { + default property alias content: d.children + property alias currentIndex: nav_list.currentIndex + property color normalColor: FluTheme.dark ? FluColors.Grey120 : FluColors.Grey120 + property color hoverColor: FluTheme.dark ? FluColors.Grey10 : FluColors.Black + id:control + width: 400 + height: 300 + implicitHeight: height + implicitWidth: width + MouseArea{ + anchors.fill: parent + preventStealing: true + } + FluObject{ + id:d + } + ListView{ + id:nav_list + height: 40 + width: control.width + model:d.children + clip: true + spacing: 20 + interactive: false + orientation: ListView.Horizontal + highlightMoveDuration: FluTheme.enableAnimation ? 167 : 0 + highlight: Item{ + clip: true + Rectangle{ + height: 3 + radius: 1.5 + color: FluTheme.primaryColor.dark + width: nav_list.currentItem ? nav_list.currentItem.width : 0 + y:37 + Behavior on width { + enabled: FluTheme.enableAnimation + NumberAnimation{ + duration: 167 + easing.type: Easing.OutCubic + } + } + } + } + delegate: Button{ + id:item_button + width: item_title.width + height: nav_list.height + background:Item{ + } + contentItem: Item{ + FluText { + id:item_title + font: FluTextStyle.Title + text: modelData.title + anchors.centerIn: parent + color: { + if(item_button.hovered) + return hoverColor + return normalColor + } + } + } + onClicked: { + nav_list.currentIndex = index + } + } + } + Item{ + id:container + anchors{ + top: nav_list.bottom + topMargin: 10 + left: parent.left + right: parent.right + bottom: parent.bottom + } + Repeater{ + model:d.children + Loader{ + property var argument: modelData.argument + anchors.fill: parent + sourceComponent: modelData.contentItem + visible: nav_list.currentIndex === index + } + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluPivotItem.qml b/src/Qt5/imports/FluentUI/Controls/FluPivotItem.qml new file mode 100644 index 00000000..4181d29c --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluPivotItem.qml @@ -0,0 +1,9 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +QtObject { + property string title + property Component contentItem + property var argument +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluPopup.qml b/src/Qt5/imports/FluentUI/Controls/FluPopup.qml new file mode 100644 index 00000000..d5a73650 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluPopup.qml @@ -0,0 +1,47 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +Popup { + id: popup + padding: 0 + modal:true + anchors.centerIn: Overlay.overlay + closePolicy: Popup.CloseOnEscape + enter: Transition { + NumberAnimation { + properties: "scale" + from:1.2 + to:1 + duration: FluTheme.enableAnimation ? 83 : 0 + easing.type: Easing.OutCubic + } + NumberAnimation { + property: "opacity" + duration: FluTheme.enableAnimation ? 83 : 0 + from:0 + to:1 + } + } + exit:Transition { + NumberAnimation { + properties: "scale" + from:1 + to:1.2 + duration: FluTheme.enableAnimation ? 83 : 0 + easing.type: Easing.OutCubic + } + NumberAnimation { + property: "opacity" + duration: FluTheme.enableAnimation ? 83 : 0 + from:1 + to:0 + } + } + background: FluRectangle{ + radius: [5,5,5,5] + color: FluTheme.dark ? Qt.rgba(43/255,43/255,43/255,1) : Qt.rgba(1,1,1,1) + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluProgressBar.qml b/src/Qt5/imports/FluentUI/Controls/FluProgressBar.qml new file mode 100644 index 00000000..3d325ec3 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluProgressBar.qml @@ -0,0 +1,60 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +ProgressBar{ + property real strokeWidth: 6 + property bool progressVisible: false + property color color: FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark + property color backgroundColor : FluTheme.dark ? Qt.rgba(99/255,99/255,99/255,1) : Qt.rgba(214/255,214/255,214/255,1) + id:control + indeterminate : true + QtObject{ + id:d + property real _radius: strokeWidth/2 + } + background: Rectangle { + implicitWidth: 150 + implicitHeight: control.strokeWidth + color: control.backgroundColor + radius: d._radius + } + contentItem: FluItem { + clip: true + radius: [d._radius,d._radius,d._radius,d._radius] + Rectangle { + id:rect_progress + width: { + if(control.indeterminate){ + return 0.5 * parent.width + } + return control.visualPosition * parent.width + } + height: parent.height + radius: d._radius + color: control.color + PropertyAnimation on x { + running: control.indeterminate && control.visible + from: -rect_progress.width + to:control.width+rect_progress.width + loops: Animation.Infinite + duration: 888 + } + } + } + FluText{ + text:(control.visualPosition * 100).toFixed(0) + "%" + visible: { + if(control.indeterminate){ + return false + } + return control.progressVisible + } + anchors{ + left: parent.left + leftMargin: control.width+5 + verticalCenter: parent.verticalCenter + } + } +} + diff --git a/src/Qt5/imports/FluentUI/Controls/FluProgressRing.qml b/src/Qt5/imports/FluentUI/Controls/FluProgressRing.qml new file mode 100644 index 00000000..c77dd256 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluProgressRing.qml @@ -0,0 +1,74 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Shapes 1.15 +import FluentUI 1.0 + +ProgressBar{ + property real strokeWidth: 6 + property bool progressVisible: false + property color color: FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark + property color backgroundColor : FluTheme.dark ? Qt.rgba(99/255,99/255,99/255,1) : Qt.rgba(214/255,214/255,214/255,1) + id:control + indeterminate : true + clip: true + background: Rectangle { + implicitWidth: 56 + implicitHeight: 56 + radius: control.width/2 + color:"transparent" + border.color: control.backgroundColor + border.width: control.strokeWidth + } + QtObject{ + id:d + property real _radius: control.width/2-control.strokeWidth/2 + property real _progress: control.indeterminate ? 0.3 : control.visualPosition + on_ProgressChanged: { + canvas.requestPaint() + } + } + Connections{ + target: FluTheme + function onDarkChanged(){ + canvas.requestPaint() + } + } + contentItem: Item { + RotationAnimation on rotation { + running: control.indeterminate && control.visible + from: 0 + to:360 + loops: Animation.Infinite + duration: 888 + } + Canvas { + id:canvas + anchors.fill: parent + antialiasing: true + renderTarget: Canvas.Image + onPaint: { + var ctx = canvas.getContext("2d") + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.save() + ctx.lineWidth = control.strokeWidth + ctx.strokeStyle = control.color + ctx.beginPath() + ctx.arc(width/2, height/2, d._radius ,-0.5 * Math.PI,-0.5 * Math.PI + d._progress * 2 * Math.PI) + ctx.stroke() + ctx.closePath() + ctx.restore() + } + } + } + FluText{ + text:(control.visualPosition * 100).toFixed(0) + "%" + visible: { + if(control.indeterminate){ + return false + } + return control.progressVisible + } + anchors.centerIn: parent + } +} + diff --git a/src/Qt5/imports/FluentUI/Controls/FluQRCode.qml b/src/Qt5/imports/FluentUI/Controls/FluQRCode.qml new file mode 100644 index 00000000..c7a8f287 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluQRCode.qml @@ -0,0 +1,23 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Item{ + property alias text: qrcode.text + property alias color: qrcode.color + property alias bgColor: qrcode.bgColor + property int size: 50 + property int margins: 0 + id:control + width: size + height: size + Rectangle{ + color: bgColor + anchors.fill: parent + } + QRCode{ + id:qrcode + size:control.size-margins + anchors.centerIn: parent + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluRadioButton.qml b/src/Qt5/imports/FluentUI/Controls/FluRadioButton.qml new file mode 100644 index 00000000..bb5b0f15 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluRadioButton.qml @@ -0,0 +1,98 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 + +Button { + property string contentDescription: "" + property bool disabled: false + property color borderNormalColor: checked ? FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark : FluTheme.dark ? Qt.rgba(161/255,161/255,161/255,1) : Qt.rgba(141/255,141/255,141/255,1) + property color borderDisableColor: FluTheme.dark ? Qt.rgba(82/255,82/255,82/255,1) : Qt.rgba(198/255,198/255,198/255,1) + property color normalColor: FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(1,1,1,1) + property color hoverColor: checked ? FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(1,1,1,1) : FluTheme.dark ? Qt.rgba(43/255,43/255,43/255,1) : Qt.rgba(222/255,222/255,222/255,1) + property color disableColor: checked ? FluTheme.dark ? Qt.rgba(159/255,159/255,159/255,1) : Qt.rgba(159/255,159/255,159/255,1) : FluTheme.dark ? Qt.rgba(43/255,43/255,43/255,1) : Qt.rgba(222/255,222/255,222/255,1) + property alias textColor: btn_text.textColor + property real size: 18 + property bool textRight: true + property real textSpacing: 6 + property var clickListener : function(){ + checked = !checked + } + Accessible.role: Accessible.Button + Accessible.name: control.text + Accessible.description: contentDescription + Accessible.onPressAction: control.clicked() + id:control + enabled: !disabled + horizontalPadding:2 + verticalPadding: 2 + background: Item{ + FluFocusRectangle{ + visible: control.activeFocus + } + } + focusPolicy:Qt.TabFocus + font:FluTextStyle.Body + onClicked: clickListener() + onCheckableChanged: { + if(checkable){ + checkable = false + } + } + contentItem: RowLayout{ + spacing: control.textSpacing + layoutDirection:control.textRight ? Qt.LeftToRight : Qt.RightToLeft + Rectangle{ + id:rect_check + width: control.size + height: control.size + radius: size/2 + border.width: { + if(checked&&!enabled){ + return 3 + } + if(pressed){ + if(checked){ + return 4 + } + return 1 + } + if(hovered){ + if(checked){ + return 3 + } + return 1 + } + return checked ? 4 : 1 + } + Behavior on border.width { + enabled: FluTheme.enableAnimation + NumberAnimation{ + duration: 167 + easing.type: Easing.OutCubic + } + } + border.color: { + if(!enabled){ + return borderDisableColor + } + return borderNormalColor + } + color:{ + if(!enabled){ + return disableColor + } + if(hovered){ + return hoverColor + } + return normalColor + } + } + FluText{ + id:btn_text + text: control.text + Layout.alignment: Qt.AlignVCenter + font: control.font + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluRadioButtons.qml b/src/Qt5/imports/FluentUI/Controls/FluRadioButtons.qml new file mode 100644 index 00000000..84b41a5b --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluRadioButtons.qml @@ -0,0 +1,29 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 + +ColumnLayout { + default property alias buttons: control.data + property int currentIndex : -1 + id:control + onCurrentIndexChanged: { + for(var i = 0;i{ + d.mouseValue = Number(mouse.x / d.itemSize)+1 + } + onExited: { + d.mouseValue = 0 + } + onClicked: (mouse)=>{ + control.value = Number(mouse.x / d.itemSize)+1 + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluRectangle.qml b/src/Qt5/imports/FluentUI/Controls/FluRectangle.qml new file mode 100644 index 00000000..215f8068 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluRectangle.qml @@ -0,0 +1,71 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtGraphicalEffects 1.15 +import FluentUI 1.0 + +Item{ + property var radius:[0,0,0,0] + property color color : FluTheme.dark ? Qt.rgba(0,0,0,1) : Qt.rgba(1,1,1,1) + property bool shadow: true + default property alias contentItem: container.data + id:control + onWidthChanged: { + canvas.requestPaint() + } + onHeightChanged: { + canvas.requestPaint() + } + onRadiusChanged: { + canvas.requestPaint() + } + FluShadow{ + anchors.fill: container + radius: control.radius[0] + visible: { + if(control.radius[0] === control.radius[1] && control.radius[0] === control.radius[2] && control.radius[0] === control.radius[3] && control.shadow){ + return true + } + return false + } + } + Rectangle{ + id:container + width: control.width + height: control.height + opacity: 0 + color:control.color + } + Canvas { + id: canvas + anchors.fill: parent + visible: false + onPaint: { + var ctx = getContext("2d") + var x = 0 + var y = 0 + var w = control.width + var h = control.height + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.save() + ctx.beginPath(); + ctx.moveTo(x + radius[0], y) + ctx.lineTo(x + w - radius[1], y) + ctx.arcTo(x + w, y, x + w, y + radius[1], radius[1]) + ctx.lineTo(x + w, y + h - radius[2]) + ctx.arcTo(x + w, y + h, x + w - radius[2], y + h, radius[2]) + ctx.lineTo(x + radius[3], y + h) + ctx.arcTo(x, y + h, x, y + h - radius[3], radius[3]) + ctx.lineTo(x, y + radius[0]) + ctx.arcTo(x, y, x + radius[0], y, radius[0]) + ctx.closePath() + ctx.fillStyle = "#000000" + ctx.fill() + ctx.restore() + } + } + OpacityMask { + anchors.fill: container + source: container + maskSource: canvas + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluRemoteLoader.qml b/src/Qt5/imports/FluentUI/Controls/FluRemoteLoader.qml new file mode 100644 index 00000000..0e5c6f53 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluRemoteLoader.qml @@ -0,0 +1,39 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +FluStatusView { + property url source: "" + property bool lazy: false + color:"transparent" + id:control + onErrorClicked: { + reload() + } + Component.onCompleted: { + if(!lazy){ + loader.source = control.source + } + } + Loader{ + id:loader + anchors.fill: parent + asynchronous: true + onStatusChanged: { + if(status === Loader.Error){ + control.statusMode = FluStatusViewType.Error + }else if(status === Loader.Loading){ + control.statusMode = FluStatusViewType.Loading + }else{ + control.statusMode = FluStatusViewType.Success + } + } + } + function reload(){ + var timestamp = Date.now(); + loader.source = control.source+"?"+timestamp + } + function itemLodaer(){ + return loader + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluScreenshot.qml b/src/Qt5/imports/FluentUI/Controls/FluScreenshot.qml new file mode 100644 index 00000000..22b7d04c --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluScreenshot.qml @@ -0,0 +1,523 @@ +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import Qt.labs.platform 1.1 +import FluentUI 1.0 + +Item{ + id:control + property int captrueMode: FluScreenshotType.Pixmap + property int dotSize: 5 + property int borderSize: 1 + property var saveFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + property color borderColor: FluTheme.primaryColor.dark + signal captrueCompleted(var captrue) + QtObject{ + id:d + property int dotMouseSize: control.dotSize+10 + property int dotMargins: -(control.dotSize-control.borderSize)/2 + property bool enablePosition: false + property int menuMargins: 6 + } + Loader { + id:loader + } + Component{ + id:com_screen + Window{ + property bool isZeroPos: screenshot.start === Qt.point(0,0) && screenshot.end === Qt.point(0,0) + id:window_screen + flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint + x:-1 + y:-1 + width: 1 + height: 1 + visible: true + color: "#00000000" + onVisibleChanged: { + if(!window_screen.visible){ + loader.sourceComponent = undefined + } + } + Component.onCompleted: { + setGeometry(0,0,screenshot_background.width,screenshot_background.height) + } + ScreenshotBackground{ + id:screenshot_background + captureMode:control.captrueMode + saveFolder: { + if(typeof control.saveFolder === 'string'){ + return control.saveFolder + }else{ + return FluTools.toLocalPath(control.saveFolder) + } + } + onCaptrueToPixmapCompleted: + (captrue)=>{ + control.captrueCompleted(captrue) + loader.sourceComponent = undefined + } + onCaptrueToFileCompleted: + (captrue)=>{ + control.captrueCompleted(captrue) + loader.sourceComponent = undefined + } + } + Screenshot{ + id:screenshot + anchors.fill: parent + } + MouseArea{ + anchors.fill: parent + acceptedButtons: Qt.RightButton | Qt.LeftButton + onPressed: + (mouse)=>{ + if(mouse.button === Qt.LeftButton){ + d.enablePosition = true + screenshot.start = Qt.point(mouse.x,mouse.y) + screenshot.end = Qt.point(mouse.x,mouse.y) + } + } + onPositionChanged: + (mouse)=>{ + if(d.enablePosition){ + screenshot.end = Qt.point(mouse.x,mouse.y) + } + } + onReleased: + (mouse)=>{ + if(mouse.button === Qt.LeftButton){ + d.enablePosition = false + screenshot.end = Qt.point(mouse.x,mouse.y) + } + } + onCanceled: + (mouse)=>{ + if(mouse.button === Qt.LeftButton){ + d.enablePosition = false + screenshot.end = Qt.point(mouse.x,mouse.y) + } + } + onClicked: + (mouse)=>{ + if (mouse.button === Qt.RightButton){ + if(isZeroPos){ + loader.sourceComponent = undefined + return + } + screenshot.start = Qt.point(0,0) + screenshot.end = Qt.point(0,0) + } + } + } + Rectangle{ + id:rect_capture + x:Math.min(screenshot.start.x,screenshot.end.x) + y:Math.min(screenshot.start.y,screenshot.end.y) + width: Math.abs(screenshot.end.x - screenshot.start.x) + height: Math.abs(screenshot.end.y - screenshot.start.y) + color:"#00000000" + border.width: control.borderSize + border.color: control.borderColor + MouseArea{ + property point clickPos: Qt.point(0,0) + anchors.fill: parent + cursorShape: Qt.SizeAllCursor + onPressed: + (mouse)=>{ + clickPos = Qt.point(mouse.x, mouse.y) + } + onPositionChanged: + (mouse)=>{ + var delta = Qt.point(mouse.x - clickPos.x,mouse.y - clickPos.y) + var w = Math.abs(screenshot.end.x - screenshot.start.x) + var h = Math.abs(screenshot.end.y - screenshot.start.y) + var x = Math.min(Math.max(rect_capture.x + delta.x,0),window_screen.width-w) + var y =Math.min(Math.max(rect_capture.y + delta.y,0),window_screen.height-h) + screenshot.start = Qt.point(x,y) + screenshot.end = Qt.point(x+w,y+h) + } + } + } + Rectangle{ + id:rect_top_left + width: control.dotSize + height: control.dotSize + color: control.borderColor + visible: !isZeroPos + anchors{ + left: rect_capture.left + leftMargin: d.dotMargins + topMargin: d.dotMargins + top: rect_capture.top + } + } + MouseArea{ + cursorShape: Qt.SizeFDiagCursor + anchors.centerIn: rect_top_left + width: d.dotMouseSize + height: d.dotMouseSize + visible: !isZeroPos + onPressed: + (mouse)=> { + FluTools.setOverrideCursor(cursorShape) + var x = rect_capture.x + var y = rect_capture.y + var w = rect_capture.width + var h = rect_capture.height + screenshot.start = Qt.point(x+w,y+h) + screenshot.end = Qt.point(x,y) + } + onReleased: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + onPositionChanged: + (mouse)=> { + screenshot.end = mapToItem(screenshot,Qt.point(mouse.x,mouse.y)) + } + onCanceled: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + } + Rectangle{ + id:rect_top_center + width: control.dotSize + height: control.dotSize + color: control.borderColor + visible: !isZeroPos + anchors{ + horizontalCenter: rect_capture.horizontalCenter + topMargin: d.dotMargins + top: rect_capture.top + } + } + MouseArea{ + cursorShape: Qt.SizeVerCursor + anchors.centerIn: rect_top_center + width: d.dotMouseSize + height: d.dotMouseSize + visible: !isZeroPos + onPressed: + (mouse)=> { + FluTools.setOverrideCursor(cursorShape) + var x = rect_capture.x + var y = rect_capture.y + var w = rect_capture.width + var h = rect_capture.height + screenshot.start = Qt.point(x+w,y+h) + screenshot.end = Qt.point(x,y) + } + onReleased: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + onPositionChanged: + (mouse)=> { + var x = rect_capture.x + screenshot.end = Qt.point(x,mapToItem(screenshot,Qt.point(mouse.x,mouse.y)).y) + } + onCanceled: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + } + Rectangle{ + id:rect_top_right + width: control.dotSize + height: control.dotSize + color: control.borderColor + visible: !isZeroPos + anchors{ + right: rect_capture.right + rightMargin: d.dotMargins + topMargin: d.dotMargins + top: rect_capture.top + } + } + MouseArea{ + cursorShape: Qt.SizeBDiagCursor + anchors.centerIn: rect_top_right + width: d.dotMouseSize + height: d.dotMouseSize + visible: !isZeroPos + onPressed: + (mouse)=> { + var x = rect_capture.x + var y = rect_capture.y + var w = rect_capture.width + var h = rect_capture.height + screenshot.start = Qt.point(x,y+h) + screenshot.end = Qt.point(x+w,y) + } + onReleased: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + onPositionChanged: + (mouse)=> { + screenshot.end = mapToItem(screenshot,Qt.point(mouse.x,mouse.y)) + } + onCanceled: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + } + Rectangle{ + id:rect_right_center + width: control.dotSize + height: control.dotSize + color: control.borderColor + visible: !isZeroPos + anchors{ + right: rect_capture.right + rightMargin: d.dotMargins + verticalCenter: rect_capture.verticalCenter + } + } + MouseArea{ + cursorShape: Qt.SizeHorCursor + anchors.centerIn: rect_right_center + width: d.dotMouseSize + height: d.dotMouseSize + visible: !isZeroPos + onPressed: + (mouse)=> { + var x = rect_capture.x + var y = rect_capture.y + var w = rect_capture.width + var h = rect_capture.height + screenshot.start = Qt.point(x,y) + screenshot.end = Qt.point(x+w,y+h) + } + onReleased: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + onPositionChanged: + (mouse)=> { + var y = rect_capture.y + var h = rect_capture.height + screenshot.end = Qt.point(mapToItem(screenshot,Qt.point(mouse.x,mouse.y)).x,y+h) + } + onCanceled: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + } + Rectangle{ + id:rect_right_bottom + width: control.dotSize + height: control.dotSize + color: control.borderColor + visible: !isZeroPos + anchors{ + right: rect_capture.right + rightMargin: d.dotMargins + bottom: rect_capture.bottom + bottomMargin: d.dotMargins + } + } + MouseArea{ + cursorShape: Qt.SizeFDiagCursor + anchors.centerIn: rect_right_bottom + width: d.dotMouseSize + height: d.dotMouseSize + visible: !isZeroPos + onPressed: + (mouse)=> { + var x = rect_capture.x + var y = rect_capture.y + var w = rect_capture.width + var h = rect_capture.height + screenshot.start = Qt.point(x,y) + screenshot.end = Qt.point(x+w,y+h) + } + onReleased: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + onPositionChanged: + (mouse)=> { + screenshot.end = mapToItem(screenshot,Qt.point(mouse.x,mouse.y)) + } + onCanceled: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + } + Rectangle{ + id:rect_bottom_center + width: control.dotSize + height: control.dotSize + color: control.borderColor + visible: !isZeroPos + anchors{ + horizontalCenter: rect_capture.horizontalCenter + bottom: rect_capture.bottom + bottomMargin: d.dotMargins + } + } + MouseArea{ + cursorShape: Qt.SizeVerCursor + anchors.centerIn: rect_bottom_center + width: d.dotMouseSize + height: d.dotMouseSize + visible: !isZeroPos + onPressed: + (mouse)=> { + var x = rect_capture.x + var y = rect_capture.y + var w = rect_capture.width + var h = rect_capture.height + screenshot.start = Qt.point(x,y) + screenshot.end = Qt.point(x+w,y+h) + } + onReleased: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + onPositionChanged: + (mouse)=> { + var x = rect_capture.x + var w = rect_capture.width + screenshot.end = Qt.point(x+w,mapToItem(screenshot,Qt.point(mouse.x,mouse.y)).y) + } + onCanceled: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + } + Rectangle{ + id:rect_bottom_left + width: control.dotSize + height: control.dotSize + color: control.borderColor + visible: !isZeroPos + anchors{ + left: rect_capture.left + leftMargin: d.dotMargins + bottom: rect_capture.bottom + bottomMargin: d.dotMargins + } + } + MouseArea{ + cursorShape: Qt.SizeBDiagCursor + anchors.centerIn: rect_bottom_left + width: d.dotMouseSize + height: d.dotMouseSize + visible: !isZeroPos + onPressed: + (mouse)=> { + var x = rect_capture.x + var y = rect_capture.y + var w = rect_capture.width + var h = rect_capture.height + screenshot.start = Qt.point(x+w,y) + screenshot.end = Qt.point(x,y+h) + } + onReleased: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + onPositionChanged: + (mouse)=> { + screenshot.end = mapToItem(screenshot,Qt.point(mouse.x,mouse.y)) + } + onCanceled: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + } + Rectangle{ + id:rect_left_center + width: control.dotSize + height: control.dotSize + color: control.borderColor + visible: !isZeroPos + anchors{ + left: rect_capture.left + leftMargin: d.dotMargins + verticalCenter: rect_capture.verticalCenter + } + } + MouseArea{ + cursorShape: Qt.SizeHorCursor + anchors.centerIn: rect_left_center + width: d.dotMouseSize + height: d.dotMouseSize + visible: !isZeroPos + onPressed: + (mouse)=> { + var x = rect_capture.x + var y = rect_capture.y + var w = rect_capture.width + var h = rect_capture.height + screenshot.start = Qt.point(x+w,y) + screenshot.end = Qt.point(x,y+h) + } + onReleased: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + onPositionChanged: + (mouse)=> { + var y = rect_capture.y + var h = rect_capture.height + screenshot.end = Qt.point(mapToItem(screenshot,Qt.point(mouse.x,mouse.y)).x,y+h) + } + onCanceled: + (mouse)=> { + FluTools.restoreOverrideCursor() + } + } + Pane{ + width: 100 + height: 40 + visible: { + if(screenshot.start === Qt.point(0,0) && screenshot.end === Qt.point(0,0)){ + return false + } + if(d.enablePosition){ + return false + } + return true + } + x:rect_capture.x + rect_capture.width - width + y:{ + if(rect_capture.y + rect_capture.height + d.menuMargins < screenshot.height-height){ + return rect_capture.y + rect_capture.height + d.menuMargins + }else if(rect_capture.y - height - d.menuMargins > 0){ + return rect_capture.y - height - d.menuMargins + }else{ + screenshot.height - height - d.menuMargins + } + } + RowLayout{ + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + FluIconButton{ + iconSource: FluentIcons.Cancel + iconSize: 18 + iconColor: Qt.rgba(247/255,75/255,77/255,1) + onClicked: { + loader.sourceComponent = undefined + } + } + FluIconButton{ + iconSource: FluentIcons.AcceptMedium + iconColor: FluTheme.primaryColor.dark + onClicked: { + screenshot_background.capture(screenshot.start,screenshot.end) + } + } + } + } + } + } + function open(){ + loader.sourceComponent = com_screen + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluScrollBar.qml b/src/Qt5/imports/FluentUI/Controls/FluScrollBar.qml new file mode 100644 index 00000000..92766a32 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluScrollBar.qml @@ -0,0 +1,85 @@ +import QtQuick 2.15 +import QtQuick.Templates 2.15 as T +import FluentUI 1.0 + +T.ScrollBar { + id: control + + property color color : FluTheme.dark ? Qt.rgba(159/255,159/255,159/255,1) : Qt.rgba(138/255,138/255,138/255,1) + property color pressedColor: FluTheme.dark ? Qt.darker(color,1.2) : Qt.lighter(color,1.2) + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + + padding: 2 + visible: control.policy !== T.ScrollBar.AlwaysOff + minimumSize: Math.max(orientation === Qt.Horizontal ? height / width : width / height,0.3) + contentItem: Item { + property bool collapsed: (control.policy === T.ScrollBar.AlwaysOn || (control.active && control.size < 1.0)) + implicitWidth: control.interactive ? 6 : 2 + implicitHeight: control.interactive ? 6 : 2 + + Rectangle{ + id:rect_bar + width: vertical ? 2 : parent.width + height: horizontal ? 2 : parent.height + color:{ + if(control.pressed){ + return control.pressedColor + } + return control .color + } + anchors{ + right: vertical ? parent.right : undefined + bottom: horizontal ? parent.bottom : undefined + } + radius: width / 2 + visible: control.size < 1.0 + } + states: [ + State{ + name:"show" + when: contentItem.collapsed + PropertyChanges { + target: rect_bar + width: vertical ? 6 : parent.width + height: horizontal ? 6 : parent.height + } + } + ,State{ + name:"hide" + when: !contentItem.collapsed + PropertyChanges { + target: rect_bar + width: vertical ? 2 : parent.width + height: horizontal ? 2 : parent.height + } + } + ] + transitions:[ + Transition { + to: "hide" + SequentialAnimation { + PauseAnimation { duration: 450 } + NumberAnimation { + target: rect_bar + properties: vertical ? "width" : "height" + duration: 167 + easing.type: Easing.OutCubic + } + } + } + ,Transition { + to: "show" + NumberAnimation { + target: rect_bar + properties: vertical ? "width" : "height" + duration: 167 + easing.type: Easing.OutCubic + } + } + ] + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluScrollIndicator.qml b/src/Qt5/imports/FluentUI/Controls/FluScrollIndicator.qml new file mode 100644 index 00000000..13e1dcb3 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluScrollIndicator.qml @@ -0,0 +1,48 @@ +import QtQuick 2.15 +import QtQuick.Templates 2.15 as T + +T.ScrollIndicator { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + + padding: 2 + + contentItem: Rectangle { + implicitWidth: 2 + implicitHeight: 2 + + color: control.palette.mid + visible: control.size < 1.0 + opacity: 0.0 + + states: State { + name: "active" + when: control.active + PropertyChanges { + target: control + contentItem.opacity: 0.75 + } + } + + transitions: [ + Transition { + from: "active" + SequentialAnimation { + PauseAnimation { + duration: 450 + } + NumberAnimation { + target: control.contentItem + duration: 200 + property: "opacity" + to: 0.0 + } + } + } + ] + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluScrollablePage.qml b/src/Qt5/imports/FluentUI/Controls/FluScrollablePage.qml new file mode 100644 index 00000000..1f3b949c --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluScrollablePage.qml @@ -0,0 +1,79 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +FluPage { + property alias title: text_title.text + default property alias content: container.data + property int spacing : 0 + property int leftPadding: 10 + property int topPadding: 0 + property int rightPadding: 10 + property int bottomPadding: 10 + property alias color: status_view.color + property alias statusMode: status_view.statusMode + property alias loadingText: status_view.loadingText + property alias emptyText:status_view.emptyText + property alias errorText:status_view.errorText + property alias errorButtonText:status_view.errorButtonText + property alias loadingItem :status_view.loadingItem + property alias emptyItem : status_view.emptyItem + property alias errorItem :status_view.errorItem + signal errorClicked + id:control + FluText{ + id:text_title + font: FluTextStyle.Title + visible: text !== "" + height: visible ? contentHeight : 0 + padding: 0 + anchors{ + top: parent.top + topMargin: control.topPadding + left: parent.left + right: parent.right + leftMargin: control.leftPadding + rightMargin: control.rightPadding + } + } + FluStatusView{ + id:status_view + color: "#00000000" + statusMode: FluStatusViewType.Success + onErrorClicked: control.errorClicked() + anchors{ + left: parent.left + right: parent.right + top: text_title.bottom + bottom: parent.bottom + bottomMargin: control.bottomPadding + } + Flickable{ + id:flickview + clip: true + anchors.fill: parent + contentWidth: parent.width + contentHeight: container.height + ScrollBar.vertical: FluScrollBar { + anchors.right: flickview.right + anchors.rightMargin: 2 + } + boundsBehavior: Flickable.StopAtBounds + ColumnLayout{ + id:container + spacing: control.spacing + clip: true + anchors{ + left: parent.left + right: parent.right + top: parent.top + leftMargin: control.leftPadding + rightMargin: control.rightPadding + } + width: parent.width + } + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluShadow.qml b/src/Qt5/imports/FluentUI/Controls/FluShadow.qml new file mode 100644 index 00000000..38f4be88 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluShadow.qml @@ -0,0 +1,66 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Item { + + property color color: FluTheme.dark ? "#FFFFFF" : "#999999" + property int radius: 4 + id:control + anchors.fill: parent + anchors.margins: -4 + Rectangle{ + width: control.width + height: control.height + anchors.centerIn: parent + color: "#00000000" + opacity: 0.02 + border.width: 1 + radius: control.radius + border.color: control.color + } + + Rectangle{ + width: control.width - 2 + height: control.height - 2 + anchors.centerIn: parent + color: "#00000000" + opacity: 0.04 + border.width: 1 + radius: control.radius + border.color: control.color + } + Rectangle{ + width: control.width - 4 + height: control.height - 4 + anchors.centerIn: parent + color: "#00000000" + opacity: 0.06 + border.width: 1 + radius: control.radius + border.color: control.color + } + + Rectangle{ + width: control.width - 6 + height: control.height - 6 + anchors.centerIn: parent + color: "#00000000" + opacity: 0.08 + border.width: 1 + radius: control.radius + border.color: control.color + } + + Rectangle{ + width: control.width - 8 + height: control.height - 8 + anchors.centerIn: parent + opacity: 0.1 + radius: control.radius + color: "#00000000" + border.width: 1 + border.color: control.color + } + +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluSlider.qml b/src/Qt5/imports/FluentUI/Controls/FluSlider.qml new file mode 100644 index 00000000..504b809d --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluSlider.qml @@ -0,0 +1,73 @@ +import QtQuick 2.15 +import QtQuick.Templates 2.15 as T +import FluentUI 1.0 + +T.Slider { + property bool tooltipEnabled: true + id: control + to:100 + stepSize:1 + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitHandleWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitHandleHeight + topPadding + bottomPadding) + padding: 6 + handle: Rectangle { + x: control.leftPadding + (control.horizontal ? control.visualPosition * (control.availableWidth - width) : (control.availableWidth - width) / 2) + y: control.topPadding + (control.horizontal ? (control.availableHeight - height) / 2 : control.visualPosition * (control.availableHeight - height)) + implicitWidth: 24 + implicitHeight: 24 + radius: 12 + color:FluTheme.dark ? Qt.rgba(69/255,69/255,69/255,1) :Qt.rgba(1,1,1,1) + FluShadow{ + radius: 12 + } + Rectangle{ + width: 24 + height: 24 + radius: 12 + color:FluTheme.dark ? FluTheme.primaryColor.lighter :FluTheme.primaryColor.dark + anchors.centerIn: parent + scale: { + if(control.pressed){ + return 4/10 + } + return control.hovered ? 6/10 : 5/10 + } + Behavior on scale { + enabled: FluTheme.enableAnimation + NumberAnimation{ + duration: 167 + easing.type: Easing.OutCubic + } + } + } + } + background: Item { + x: control.leftPadding + (control.horizontal ? 0 : (control.availableWidth - width) / 2) + y: control.topPadding + (control.horizontal ? (control.availableHeight - height) / 2 : 0) + implicitWidth: control.horizontal ? 180 : 6 + implicitHeight: control.horizontal ? 6 : 180 + width: control.horizontal ? control.availableWidth : implicitWidth + height: control.horizontal ? implicitHeight : control.availableHeight + Rectangle{ + anchors.fill: parent + anchors.margins: 1 + radius: 2 + color:FluTheme.dark ? Qt.rgba(162/255,162/255,162/255,1) : Qt.rgba(138/255,138/255,138/255,1) + } + scale: control.horizontal && control.mirrored ? -1 : 1 + Rectangle { + y: control.horizontal ? 0 : control.visualPosition * parent.height + width: control.horizontal ? control.position * parent.width : 6 + height: control.horizontal ? 6 : control.position * parent.height + radius: 3 + color:FluTheme.dark ? FluTheme.primaryColor.lighter :FluTheme.primaryColor.dark + } + } + FluTooltip{ + parent: control.handle + visible: control.tooltipEnabled && control.pressed + text:String(control.value) + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluSpinBox.qml b/src/Qt5/imports/FluentUI/Controls/FluSpinBox.qml new file mode 100644 index 00000000..917e2f66 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluSpinBox.qml @@ -0,0 +1,156 @@ +import QtQuick 2.15 +import QtQuick.Templates 2.15 as T +import FluentUI 1.0 + +T.SpinBox { + id: control + property bool disabled: false + property color normalColor: FluTheme.dark ? Qt.rgba(56/255,56/255,56/255,1) : Qt.rgba(232/255,232/255,232/255,1) + property color hoverColor: FluTheme.dark ? Qt.rgba(64/255,64/255,64/255,1) : Qt.rgba(224/255,224/255,224/255,1) + property color pressedColor: FluTheme.dark ? Qt.rgba(72/255,72/255,72/255,1) : Qt.rgba(216/255,216/255,216/255,1) + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding, + up.implicitIndicatorHeight, down.implicitIndicatorHeight) + leftPadding: padding + (control.mirrored ? (up.indicator ? up.indicator.width : 0) : (down.indicator ? down.indicator.width : 0)) + rightPadding: padding + (control.mirrored ? (down.indicator ? down.indicator.width : 0) : (up.indicator ? up.indicator.width : 0)) + enabled: !disabled + validator: IntValidator { + locale: control.locale.name + bottom: Math.min(control.from, control.to) + top: Math.max(control.from, control.to) + } + + contentItem: TextInput { + property color normalColor: FluTheme.dark ? Qt.rgba(255/255,255/255,255/255,1) : Qt.rgba(27/255,27/255,27/255,1) + property color disableColor: FluTheme.dark ? Qt.rgba(131/255,131/255,131/255,1) : Qt.rgba(160/255,160/255,160/255,1) + property color placeholderNormalColor: FluTheme.dark ? Qt.rgba(210/255,210/255,210/255,1) : Qt.rgba(96/255,96/255,96/255,1) + property color placeholderFocusColor: FluTheme.dark ? Qt.rgba(152/255,152/255,152/255,1) : Qt.rgba(141/255,141/255,141/255,1) + property color placeholderDisableColor: FluTheme.dark ? Qt.rgba(131/255,131/255,131/255,1) : Qt.rgba(160/255,160/255,160/255,1) + z: 2 + text: control.displayText + clip: width < implicitWidth + padding: 6 + font: control.font + color: { + if(!enabled){ + return disableColor + } + return normalColor + } + selectionColor: FluTools.colorAlpha(FluTheme.primaryColor.lightest,0.6) + selectedTextColor: color + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + readOnly: !control.editable + validator: control.validator + inputMethodHints: control.inputMethodHints + Rectangle{ + width: parent.width + height: contentItem.activeFocus ? 3 : 1 + anchors.bottom: parent.bottom + visible: contentItem.enabled + color: { + if(FluTheme.dark){ + contentItem.activeFocus ? FluTheme.primaryColor.lighter : Qt.rgba(166/255,166/255,166/255,1) + }else{ + return contentItem.activeFocus ? FluTheme.primaryColor.dark : Qt.rgba(183/255,183/255,183/255,1) + } + } + Behavior on height{ + enabled: FluTheme.enableAnimation + NumberAnimation{ + duration: 83 + easing.type: Easing.OutCubic + } + } + } + } + + up.indicator: FluItem { + x: control.mirrored ? 0 : control.width - width + height: control.height + implicitWidth: 32 + implicitHeight: 32 + radius: [0,4,4,0] + Rectangle{ + anchors.fill: parent + color: { + if(control.up.pressed){ + return control.pressedColor + } + if(control.up.hovered){ + return control.hoverColor + } + return control.normalColor + } + } + Rectangle { + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + width: parent.width / 3 + height: 2 + color: enabled ? FluTheme.dark ? Qt.rgba(1,1,1,1) : Qt.rgba(0,0,0,1) : FluColors.Grey90 + } + Rectangle { + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + width: 2 + height: parent.width / 3 + color: enabled ? FluTheme.dark ? Qt.rgba(1,1,1,1) : Qt.rgba(0,0,0,1) : FluColors.Grey90 + } + } + + + down.indicator: FluItem { + x: control.mirrored ? parent.width - width : 0 + height: control.height + implicitWidth: 32 + implicitHeight: 32 + radius: [4,0,0,4] + Rectangle{ + anchors.fill: parent + color: { + if(control.down.pressed){ + return control.pressedColor + } + if(control.down.hovered){ + return control.hoverColor + } + return normalColor + } + } + Rectangle { + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + width: parent.width / 3 + height: 2 + color: enabled ? FluTheme.dark ? Qt.rgba(1,1,1,1) : Qt.rgba(0,0,0,1) : FluColors.Grey90 + } + } + + background: Rectangle { + implicitWidth: 136 + radius: 4 + border.width: 1 + border.color: { + if(contentItem.disabled){ + return FluTheme.dark ? Qt.rgba(73/255,73/255,73/255,1) : Qt.rgba(237/255,237/255,237/255,1) + } + return FluTheme.dark ? Qt.rgba(76/255,76/255,76/255,1) : Qt.rgba(240/255,240/255,240/255,1) + } + color: { + if(contentItem.disabled){ + return FluTheme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(252/255,252/255,252/255,1) + } + if(contentItem.activeFocus){ + return FluTheme.dark ? Qt.rgba(36/255,36/255,36/255,1) : Qt.rgba(1,1,1,1) + } + if(contentItem.hovered){ + return FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1) + } + return FluTheme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(1,1,1,1) + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluStatusView.qml b/src/Qt5/imports/FluentUI/Controls/FluStatusView.qml new file mode 100644 index 00000000..6fac6d3d --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluStatusView.qml @@ -0,0 +1,116 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 + +Item{ + id:control + default property alias content: container.data + property int statusMode: FluStatusViewType.Loading + property string loadingText:"正在加载..." + property string emptyText: "空空如也" + property string errorText: "页面出错了.." + property string errorButtonText: "重新加载" + property color color: FluTheme.dark ? Window.active ? Qt.rgba(38/255,44/255,54/255,1) : Qt.rgba(39/255,39/255,39/255,1) : Qt.rgba(251/255,251/255,253/255,1) + signal errorClicked + property Component loadingItem : com_loading + property Component emptyItem : com_empty + property Component errorItem : com_error + + Item{ + id:container + anchors.fill: parent + visible: statusMode===FluStatusViewType.Success + } + Loader{ + id:loader + anchors.fill: parent + visible: statusMode!==FluStatusViewType.Success + sourceComponent: { + if(statusMode === FluStatusViewType.Loading){ + return loadingItem + } + if(statusMode === FluStatusViewType.Empty){ + return emptyItem + } + if(statusMode === FluStatusViewType.Error){ + return errorItem + } + return undefined + } + } + Component{ + id:com_loading + FluArea{ + paddings: 0 + border.width: 0 + radius: 0 + color:control.color + ColumnLayout{ + anchors.centerIn: parent + FluProgressRing{ + indeterminate: true + Layout.alignment: Qt.AlignHCenter + } + FluText{ + text:control.loadingText + Layout.alignment: Qt.AlignHCenter + } + } + } + } + Component { + id:com_empty + FluArea{ + paddings: 0 + border.width: 0 + radius: 0 + color:control.color + ColumnLayout{ + anchors.centerIn: parent + FluText{ + text:control.emptyText + font: FluTextStyle.BodyStrong + Layout.alignment: Qt.AlignHCenter + } + } + } + } + Component{ + id:com_error + FluArea{ + paddings: 0 + border.width: 0 + radius: 0 + color:control.color + ColumnLayout{ + anchors.centerIn: parent + FluText{ + text:control.errorText + font: FluTextStyle.BodyStrong + Layout.alignment: Qt.AlignHCenter + } + FluFilledButton{ + id:btn_error + Layout.alignment: Qt.AlignHCenter + text:control.errorButtonText + onClicked:{ + control.errorClicked() + } + } + } + } + } + function showSuccessView(){ + statusMode = FluStatusViewType.Success + } + function showLoadingView(){ + statusMode = FluStatusViewType.Loading + } + function showEmptyView(){ + statusMode = FluStatusViewType.Empty + } + function showErrorView(){ + statusMode = FluStatusViewType.Error + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluTabView.qml b/src/Qt5/imports/FluentUI/Controls/FluTabView.qml new file mode 100644 index 00000000..7554a390 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluTabView.qml @@ -0,0 +1,307 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 + +Item { + property int tabWidthBehavior : FluTabViewType.Equal + property int closeButtonVisibility : FluTabViewType.Always + property int itemWidth: 146 + property bool addButtonVisibility: true + signal newPressed + id:control + implicitHeight: height + implicitWidth: width + anchors.fill: { + if(parent) + return parent + return undefined + } + QtObject { + id: d + property int dragIndex: -1 + property bool dragBehavior: false + property bool itemPress: false + property int maxEqualWidth: 240 + } + MouseArea{ + anchors.fill: parent + preventStealing: true + } + ListModel{ + id:tab_model + } + FluIconButton{ + id:btn_new + visible: addButtonVisibility + width: 34 + height: 34 + x:Math.min(tab_nav.contentWidth,tab_nav.width) + anchors.top: parent.top + iconSource: FluentIcons.Add + onClicked: { + newPressed() + } + } + ListView{ + id:tab_nav + height: 34 + orientation: ListView.Horizontal + anchors{ + top: parent.top + left: parent.left + right: parent.right + rightMargin: 34 + } + interactive: false + model: tab_model + move: Transition { + NumberAnimation { properties: "x"; duration: 100; easing.type: Easing.OutCubic } + NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.OutCubic } + } + moveDisplaced: Transition { + NumberAnimation { properties: "x"; duration: 300; easing.type: Easing.OutCubic} + NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.OutCubic } + } + clip: true + ScrollBar.horizontal: ScrollBar{ + id: scroll_nav + policy: ScrollBar.AlwaysOff + } + delegate: Item{ + width: item_layout.width + height: item_container.height + z: item_mouse_drag.pressed ? 1000 : 1 + Item{ + id:item_layout + width: item_container.width + height: item_container.height + FluItem{ + id:item_container + property real timestamp: new Date().getTime() + height: tab_nav.height + width: { + if(tabWidthBehavior === FluTabViewType.Equal){ + return Math.max(Math.min(d.maxEqualWidth,tab_nav.width/tab_nav.count),41 + item_btn_close.width) + } + if(tabWidthBehavior === FluTabViewType.SizeToContent){ + return itemWidth + } + if(tabWidthBehavior === FluTabViewType.Compact){ + return item_mouse_hove.containsMouse || item_btn_close.hovered || tab_nav.currentIndex === index ? itemWidth : 41 + item_btn_close.width + } + return Math.max(Math.min(d.maxEqualWidth,tab_nav.width/tab_nav.count),41 + item_btn_close.width) + } + radius: [6,6,0,0] + Behavior on x { enabled: d.dragBehavior; NumberAnimation { duration: 200 } } + Behavior on y { enabled: d.dragBehavior; NumberAnimation { duration: 200 } } + MouseArea{ + id:item_mouse_hove + anchors.fill: parent + hoverEnabled: true + } + FluTooltip{ + visible: item_mouse_hove.containsMouse + text:item_text.text + delay: 1000 + } + MouseArea{ + id:item_mouse_drag + anchors.fill: parent + drag.target: item_container + drag.axis: Drag.XAxis + onWheel: (wheel)=>{ + if (wheel.angleDelta.y > 0) scroll_nav.decrease() + else scroll_nav.increase() + } + onPressed: { + d.itemPress = true + item_container.timestamp = new Date().getTime(); + d.dragBehavior = false; + var pos = tab_nav.mapFromItem(item_container, 0, 0) + d.dragIndex = model.index + item_container.parent = tab_nav + item_container.x = pos.x + item_container.y = pos.y + } + onReleased: { + d.itemPress = false + timer.stop() + var timeDiff = new Date().getTime() - item_container.timestamp + if (timeDiff < 300) { + tab_nav.currentIndex = index + } + d.dragIndex = -1; + var pos = tab_nav.mapToItem(item_layout, item_container.x, item_container.y) + item_container.parent = item_layout; + item_container.x = pos.x; + item_container.y = pos.y; + d.dragBehavior = true; + item_container.x = 0; + item_container.y = 0; + } + onPositionChanged: { + var pos = tab_nav.mapFromItem(item_container, 0, 0) + updatePosition(pos) + if(pos.x<0){ + timer.isIncrease = false + timer.restart() + }else if(pos.x>tab_nav.width-itemWidth){ + timer.isIncrease = true + timer.restart() + }else{ + timer.stop() + } + } + Timer{ + id:timer + property bool isIncrease: true + interval: 10 + repeat: true + onTriggered: { + if(isIncrease){ + if(tab_nav.contentX>=tab_nav.contentWidth-tab_nav.width){ + return + } + tab_nav.contentX = tab_nav.contentX+2 + }else{ + if(tab_nav.contentX<=0){ + return + } + tab_nav.contentX = tab_nav.contentX-2 + } + item_mouse_drag.updatePosition(tab_nav.mapFromItem(item_container, 0, 0)) + } + } + function updatePosition(pos){ + var idx = tab_nav.indexAt(pos.x+tab_nav.contentX+1, pos.y) + var firstIdx = tab_nav.indexAt(tab_nav.contentX+1, pos.y) + var lastIdx = tab_nav.indexAt(tab_nav.width+tab_nav.contentX-1, pos.y) + if(lastIdx === -1){ + lastIdx = tab_nav.count-1 + } + if (idx!==-1 && idx >= firstIdx && idx <= lastIdx && d.dragIndex !== idx) { + tab_model.move(d.dragIndex, idx, 1) + d.dragIndex = idx; + } + } + } + Rectangle{ + anchors.fill: parent + color: { + if(FluTheme.dark){ + if(item_mouse_hove.containsMouse || item_btn_close.hovered){ + return Qt.rgba(1,1,1,0.03) + } + if(tab_nav.currentIndex === index){ + return Qt.rgba(1,1,1,0.06) + } + return Qt.rgba(0,0,0,0) + }else{ + if(item_mouse_hove.containsMouse || item_btn_close.hovered){ + return Qt.rgba(0,0,0,0.03) + } + if(tab_nav.currentIndex === index){ + return Qt.rgba(0,0,0,0.06) + } + return Qt.rgba(0,0,0,0) + } + } + } + RowLayout{ + spacing: 0 + height: parent.height + Image{ + source:model.icon + Layout.leftMargin: 10 + Layout.preferredWidth: 14 + Layout.preferredHeight: 14 + Layout.alignment: Qt.AlignVCenter + } + FluText{ + id:item_text + text: model.text + Layout.leftMargin: 10 + visible: { + if(tabWidthBehavior === FluTabViewType.Equal){ + return true + } + if(tabWidthBehavior === FluTabViewType.SizeToContent){ + return true + } + if(tabWidthBehavior === FluTabViewType.Compact){ + return item_mouse_hove.containsMouse || item_btn_close.hovered || tab_nav.currentIndex === index + } + return false + } + Layout.preferredWidth: visible?item_container.width - 41 - item_btn_close.width:0 + elide: Text.ElideRight + Layout.alignment: Qt.AlignVCenter + } + } + FluIconButton{ + id:item_btn_close + iconSource: FluentIcons.ChromeClose + iconSize: 10 + width: visible ? 24 : 0 + height: 24 + visible: { + if(closeButtonVisibility === FluTabViewType.Nerver) + return false + if(closeButtonVisibility === FluTabViewType.OnHover) + return item_mouse_hove.containsMouse || item_btn_close.hovered + return true + } + anchors{ + right: parent.right + rightMargin: 5 + verticalCenter: parent.verticalCenter + } + onClicked: { + tab_model.remove(index) + } + } + FluDivider{ + width: 1 + height: 16 + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + } + } + } + } + } + } + Item{ + id:container + anchors{ + top: tab_nav.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } + Repeater{ + model:tab_model + Loader{ + property var argument: model.argument + anchors.fill: parent + sourceComponent: model.page + visible: tab_nav.currentIndex === index + } + } + } + function createTab(icon,text,page,argument={}){ + return {icon:icon,text:text,page:page,argument:argument} + } + function appendTab(icon,text,page,argument){ + tab_model.append(createTab(icon,text,page,argument)) + } + function setTabList(list){ + tab_model.clear() + tab_model.append(list) + } + function count(){ + return tab_nav.count + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluTableModelColumn.qml b/src/Qt5/imports/FluentUI/Controls/FluTableModelColumn.qml new file mode 100644 index 00000000..b3a1fd01 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluTableModelColumn.qml @@ -0,0 +1,5 @@ +import Qt.labs.qmlmodels 1.0 + +TableModelColumn{ + +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluTableView.qml b/src/Qt5/imports/FluentUI/Controls/FluTableView.qml new file mode 100644 index 00000000..6be63d77 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluTableView.qml @@ -0,0 +1,531 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import Qt.labs.qmlmodels 1.0 +import FluentUI 1.0 + +Rectangle { + property var columnSource + property var dataSource + property color selectionColor: FluTools.colorAlpha(FluTheme.primaryColor.lightest,0.6) + property color hoverButtonColor: FluTools.colorAlpha(selectionColor,0.2) + property color pressedButtonColor: FluTools.colorAlpha(selectionColor,0.4) + id:control + color: FluTheme.dark ? Qt.rgba(39/255,39/255,39/255,1) : Qt.rgba(251/255,251/255,253/255,1) + onColumnSourceChanged: { + if(columnSource.length!==0){ + var com_column = Qt.createComponent("FluTableModelColumn.qml") + if (com_column.status === Component.Ready) { + var columns= [] + var header_rows = {} + columnSource.forEach(function(item){ + var column = com_column.createObject(table_model,{display:item.dataIndex}); + columns.push(column) + header_rows[item.dataIndex] = item.title + }) + table_model.columns = columns + header_model.columns = columns + d.header_rows = [header_rows] + } + } + } + QtObject{ + id:d + property int defaultItemWidth: 100 + property int defaultItemHeight: 42 + property var header_rows:[] + property bool selectionFlag: true + function obtEditDelegate(column,row){ + var display = table_model.data(table_model.index(row,column),"display") + var cellItem = table_view.itemAtCell(column, row) + var cellPosition = cellItem.mapToItem(scroll_table, 0, 0) + item_loader.column = column + item_loader.row = row + item_loader.x = table_view.contentX + cellPosition.x + item_loader.y = table_view.contentY + cellPosition.y + item_loader.width = table_view.columnWidthProvider(column) + item_loader.height = table_view.rowHeightProvider(row) + item_loader.display = display + var obj =columnSource[column].editDelegate + if(obj){ + return obj + } + if(columnSource[column].editMultiline === true){ + return com_edit_multiline + } + return com_edit + } + } + onDataSourceChanged: { + table_model.clear() + dataSource.forEach(function(item){ + table_model.appendRow(item) + }) + } + TableModel { + id:table_model + } + Component{ + id:com_edit + FluTextBox{ + text: display + readOnly: true === columnSource[column].readOnly + Component.onCompleted: { + forceActiveFocus() + selectAll() + } + onCommit: { + if(!readOnly){ + display = text + } + tableView.closeEditor() + } + } + } + Component{ + id:com_edit_multiline + Item{ + anchors.fill: parent + ScrollView{ + id:item_scroll + clip: true + anchors.fill: parent + ScrollBar.vertical: FluScrollBar{ + parent: item_scroll + x: item_scroll.mirrored ? 0 : item_scroll.width - width + y: item_scroll.topPadding + height: item_scroll.availableHeight + active: item_scroll.ScrollBar.horizontal.active + } + FluMultilineTextBox { + id:text_box + text: display + readOnly: true === columnSource[column].readOnly + verticalAlignment: TextInput.AlignVCenter + Component.onCompleted: { + forceActiveFocus() + selectAll() + } + rightPadding: 24 + onCommit: { + if(!readOnly){ + display = text + } + tableView.closeEditor() + } + } + } + FluIconButton{ + iconSource:FluentIcons.ChromeClose + iconSize: 10 + width: 20 + height: 20 + visible: { + if(text_box.readOnly) + return false + return text_box.text !== "" + } + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: 5 + } + onClicked:{ + text_box.text = "" + } + } + } + } + Component{ + id:com_text + FluText { + id:item_text + text: itemData + anchors.fill: parent + anchors.margins: 10 + elide: Text.ElideRight + wrapMode: Text.WrapAnywhere + verticalAlignment: Text.AlignVCenter + HoverHandler{ + id: hover_handler + } + FluTooltip{ + text: item_text.text + delay: 500 + visible: item_text.contentWidth < item_text.implicitWidth && item_text.contentHeight < item_text.implicitHeight && hover_handler.hovered + } + } + } + ScrollView{ + id:scroll_table + anchors.left: header_vertical.right + anchors.top: header_horizontal.bottom + anchors.right: parent.right + anchors.bottom: parent.bottom + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AlwaysOff + TableView { + id:table_view + ListModel{ + id:model_columns + } + boundsBehavior: Flickable.StopAtBounds + ScrollBar.horizontal: FluScrollBar{ + id:scroll_bar_h + } + ScrollBar.vertical: FluScrollBar{ + id:scroll_bar_v + } + columnWidthProvider: function(column) { + var w = columnSource[column].width + if(!w){ + w = columnSource[column].minimumWidth + } + if(!w){ + w = d.defaultItemWidth + } + if(column === item_loader.column){ + item_loader.width = w + } + if(column === item_loader.column-1){ + let cellItem = table_view.itemAtCell(item_loader.column, item_loader.row) + if(cellItem){ + let cellPosition = cellItem.mapToItem(scroll_table, 0, 0) + item_loader.x = table_view.contentX + cellPosition.x + } + } + return w + } + rowHeightProvider: function(row) { + if(row>=table_model.rowCount){ + return 0 + } + var h = table_model.getRow(row).height + if(!h){ + h = table_model.getRow(row).minimumHeight + } + if(!h){ + return d.defaultItemHeight + } + if(row === item_loader.row){ + item_loader.height = h + } + if(row === item_loader.row-1){ + let cellItem = table_view.itemAtCell(item_loader.column, item_loader.row) + if(cellItem){ + let cellPosition = cellItem.mapToItem(scroll_table, 0, 0) + item_loader.y = table_view.contentY + cellPosition.y + } + } + return h + } + model: table_model + clip: true + delegate: Rectangle { + id:item_table + property var position: Qt.point(column,row) + color: (row%2!==0) ? control.color : (FluTheme.dark ? Qt.rgba(1,1,1,0.06) : Qt.rgba(0,0,0,0.06)) + implicitHeight: 40 + implicitWidth: { + var w = columnSource[column].width + if(!w){ + w = columnSource[column].minimumWidth + } + if(!w){ + w = d.defaultItemWidth + } + return w + } + Rectangle{ + anchors.fill: parent + visible: !item_loader.sourceComponent + color: "#00000000" + } + MouseArea{ + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onPressed:{ + closeEditor() + table_view.interactive = false + } + onCanceled: { + table_view.interactive = true + } + onReleased: { + table_view.interactive = true + } + onDoubleClicked:{ + if(display instanceof Component){ + return + } + item_loader.sourceComponent = d.obtEditDelegate(column,row) + } + onClicked: + (event)=>{ + item_loader.sourceComponent = undefined + d.selectionFlag = !d.selectionFlag + event.accepted = true + } + } + Loader{ + property var itemData: display + property var tableView: table_view + property var tableModel: table_model + property var position: item_table.position + property int row: position.y + property int column: position.x + anchors.fill: parent + sourceComponent: { + if(itemData instanceof Component){ + return itemData + } + return com_text + } + } + } + } + Loader{ + id:item_loader + z:2 + property var display + property int column + property int row + property var tableView: control + sourceComponent: undefined + onDisplayChanged: { + var obj = table_model.getRow(row) + obj[columnSource[column].dataIndex] = display + table_model.setRow(row,obj) + } + } + } + Component{ + id:com_handle + Item {} + } + TableView { + id: header_horizontal + model: TableModel{ + id:header_model + rows: d.header_rows + } + syncDirection: Qt.Horizontal + anchors.left: scroll_table.left + anchors.top: parent.top + implicitWidth: syncView ? syncView.width : 0 + implicitHeight: Math.max(1, contentHeight) + syncView: table_view + boundsBehavior: Flickable.StopAtBounds + clip: true + delegate: Rectangle { + id:column_item_control + readonly property real cellPadding: 8 + property bool canceled: false + readonly property var obj : columnSource[column] + implicitWidth: column_text.implicitWidth + (cellPadding * 2) + implicitHeight: Math.max(36, column_text.implicitHeight + (cellPadding * 2)) + color:{ + d.selectionFlag + if(column_item_control_mouse.pressed){ + return control.pressedButtonColor + } + return column_item_control_mouse.containsMouse&&!canceled ? control.hoverButtonColor : FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(247/255,247/255,247/255,1) + } + border.color: FluTheme.dark ? "#252525" : "#e4e4e4" + FluText { + id: column_text + text: model.display + width: parent.width + height: parent.height + font.bold:{ + d.selectionFlag + return true + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + MouseArea{ + id:column_item_control_mouse + anchors.fill: parent + anchors.rightMargin: 6 + hoverEnabled: true + onCanceled: { + column_item_control.canceled = true + } + onContainsMouseChanged: { + if(!containsMouse){ + column_item_control.canceled = false + } + } + onClicked: + (event)=>{ + closeEditor() + d.selectionFlag = !d.selectionFlag + } + } + MouseArea{ + property point clickPos: "0,0" + height: parent.height + width: 6 + anchors.right: parent.right + acceptedButtons: Qt.LeftButton + hoverEnabled: true + visible: !(obj.width === obj.minimumWidth && obj.width === obj.maximumWidth && obj.width) + cursorShape: Qt.SplitHCursor + onPressed : + (mouse)=>{ + header_horizontal.interactive = false + FluTools.setOverrideCursor(Qt.SplitHCursor) + clickPos = Qt.point(mouse.x, mouse.y) + } + onReleased:{ + header_horizontal.interactive = true + FluTools.restoreOverrideCursor() + } + onCanceled: { + header_horizontal.interactive = true + FluTools.restoreOverrideCursor() + } + onPositionChanged: + (mouse)=>{ + if(!pressed){ + return + } + var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y) + var minimumWidth = obj.minimumWidth + var maximumWidth = obj.maximumWidth + var w = obj.width + if(!w){ + w = d.defaultItemWidth + } + if(!minimumWidth){ + minimumWidth = d.defaultItemWidth + } + if(!maximumWidth){ + maximumWidth = 65535 + } + obj.width = Math.min(Math.max(minimumWidth, w + delta.x),maximumWidth) + table_view.forceLayout() + } + } + } + } + TableView { + id: header_vertical + boundsBehavior: Flickable.StopAtBounds + anchors.top: scroll_table.top + anchors.left: parent.left + implicitWidth: Math.max(1, contentWidth) + implicitHeight: syncView ? syncView.height : 0 + syncDirection: Qt.Vertical + syncView: table_view + clip: true + model: TableModel{ + TableModelColumn {} + rows: { + if(dataSource) + return dataSource + return [] + } + } + delegate: Rectangle{ + id:item_control + readonly property real cellPadding: 8 + property bool canceled: false + implicitWidth: Math.max(30, row_text.implicitWidth + (cellPadding * 2)) + implicitHeight: row_text.implicitHeight + (cellPadding * 2) + color: { + d.selectionFlag + if(item_control_mouse.pressed){ + return control.pressedButtonColor + } + return item_control_mouse.containsMouse&&!canceled ? control.hoverButtonColor : FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(247/255,247/255,247/255,1) + } + border.color: FluTheme.dark ? "#252525" : "#e4e4e4" + FluText{ + id:row_text + anchors.centerIn: parent + text: row + 1 + font.bold:{ + d.selectionFlag + return true + } + } + MouseArea{ + id:item_control_mouse + anchors.fill: parent + anchors.bottomMargin: 6 + hoverEnabled: true + onCanceled: { + item_control.canceled = true + } + onContainsMouseChanged: { + if(!containsMouse){ + item_control.canceled = false + } + } + onClicked: + (event)=>{ + closeEditor() + d.selectionFlag = !d.selectionFlag + } + } + MouseArea{ + property point clickPos: "0,0" + height: 6 + width: parent.width + anchors.bottom: parent.bottom + acceptedButtons: Qt.LeftButton + cursorShape: Qt.SplitVCursor + visible: { + var obj = table_model.getRow(row) + return !(obj.height === obj.minimumHeight && obj.height === obj.maximumHeight && obj.height) + } + onPressed : + (mouse)=>{ + header_vertical.interactive = false + FluTools.setOverrideCursor(Qt.SplitVCursor) + clickPos = Qt.point(mouse.x, mouse.y) + } + onReleased:{ + header_vertical.interactive = true + FluTools.restoreOverrideCursor() + } + onCanceled: { + header_vertical.interactive = true + FluTools.restoreOverrideCursor() + } + onPositionChanged: + (mouse)=>{ + if(!pressed){ + return + } + var obj = table_model.getRow(row) + var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y) + var minimumHeight = obj.minimumHeight + var maximumHeight = obj.maximumHeight + var h = obj.height + if(!h){ + h = d.defaultItemHeight + } + if(!minimumHeight){ + minimumHeight = d.defaultItemHeight + } + if(!maximumHeight){ + maximumHeight = 65535 + } + obj.height = Math.min(Math.max(minimumHeight, h + delta.y),maximumHeight) + table_model.setRow(row,obj) + table_view.forceLayout() + } + } + } + } + function closeEditor(){ + item_loader.sourceComponent = null + } + function resetPosition(){ + scroll_bar_h.position = 0 + scroll_bar_v.position = 0 + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluText.qml b/src/Qt5/imports/FluentUI/Controls/FluText.qml new file mode 100644 index 00000000..414f1e06 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluText.qml @@ -0,0 +1,11 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Text { + property color textColor: FluTheme.dark ? FluColors.White : FluColors.Grey220 + id:text + color: textColor + renderType: FluTheme.nativeText ? Text.NativeRendering : Text.QtRendering + font: FluTextStyle.Body +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluTextBox.qml b/src/Qt5/imports/FluentUI/Controls/FluTextBox.qml new file mode 100644 index 00000000..e2b28bc8 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluTextBox.qml @@ -0,0 +1,101 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +TextField{ + signal commit(string text) + property bool disabled: false + property int iconSource: 0 + property color normalColor: FluTheme.dark ? Qt.rgba(255/255,255/255,255/255,1) : Qt.rgba(27/255,27/255,27/255,1) + property color disableColor: FluTheme.dark ? Qt.rgba(131/255,131/255,131/255,1) : Qt.rgba(160/255,160/255,160/255,1) + property color placeholderNormalColor: FluTheme.dark ? Qt.rgba(210/255,210/255,210/255,1) : Qt.rgba(96/255,96/255,96/255,1) + property color placeholderFocusColor: FluTheme.dark ? Qt.rgba(152/255,152/255,152/255,1) : Qt.rgba(141/255,141/255,141/255,1) + property color placeholderDisableColor: FluTheme.dark ? Qt.rgba(131/255,131/255,131/255,1) : Qt.rgba(160/255,160/255,160/255,1) + property int iconRightMargin: icon_end.visible ? 25 : 5 + property bool cleanEnabled: true + id:control + padding: 8 + leftPadding: padding+2 + enabled: !disabled + color: { + if(!enabled){ + return disableColor + } + return normalColor + } + font:FluTextStyle.Body + renderType: FluTheme.nativeText ? Text.NativeRendering : Text.QtRendering + selectionColor:FluTools.colorAlpha(FluTheme.primaryColor.lightest,0.6) + selectedTextColor: color + placeholderTextColor: { + if(!enabled){ + return placeholderDisableColor + } + if(focus){ + return placeholderFocusColor + } + return placeholderNormalColor + } + selectByMouse: true + rightPadding: icon_end.visible ? 50 : 30 + background: FluTextBoxBackground{ + inputItem: control + implicitWidth: 240 + FluIcon{ + id:icon_end + iconSource: control.iconSource + iconSize: 15 + opacity: 0.5 + visible: control.iconSource != 0 + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: 5 + } + } + } + Keys.onEnterPressed: (event)=> d.handleCommit(event) + Keys.onReturnPressed:(event)=> d.handleCommit(event) + QtObject{ + id:d + function handleCommit(event){ + control.commit(control.text) + } + } + MouseArea{ + anchors.fill: parent + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.RightButton + onClicked: control.echoMode !== TextInput.Password && menu.popup() + } + FluIconButton{ + iconSource:FluentIcons.ChromeClose + iconSize: 10 + width: 20 + height: 20 + verticalPadding: 0 + horizontalPadding: 0 + visible: { + if(control.cleanEnabled === false){ + return false + } + if(control.readOnly) + return false + return control.text !== "" + } + anchors{ + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: control.iconRightMargin + } + contentDescription:"清空" + onClicked:{ + control.text = "" + } + } + FluTextBoxMenu{ + id:menu + inputItem: control + } +} + diff --git a/src/Qt5/imports/FluentUI/Controls/FluTextBoxBackground.qml b/src/Qt5/imports/FluentUI/Controls/FluTextBoxBackground.qml new file mode 100644 index 00000000..64d0698a --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluTextBoxBackground.qml @@ -0,0 +1,57 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtGraphicalEffects 1.15 +import FluentUI 1.0 + +Rectangle{ + property Item inputItem + id:content + radius: 4 + layer.enabled: true + color: { + if(inputItem.disabled){ + return FluTheme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(252/255,252/255,252/255,1) + } + if(inputItem.activeFocus){ + return FluTheme.dark ? Qt.rgba(36/255,36/255,36/255,1) : Qt.rgba(1,1,1,1) + } + if(inputItem.hovered){ + return FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1) + } + return FluTheme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(1,1,1,1) + } + layer.effect:OpacityMask { + maskSource: Rectangle { + width: content.width + height: content.height + radius: 4 + } + } + border.width: 1 + border.color: { + if(inputItem.disabled){ + return FluTheme.dark ? Qt.rgba(73/255,73/255,73/255,1) : Qt.rgba(237/255,237/255,237/255,1) + } + return FluTheme.dark ? Qt.rgba(76/255,76/255,76/255,1) : Qt.rgba(240/255,240/255,240/255,1) + } + Rectangle{ + width: parent.width + height: inputItem.activeFocus ? 3 : 1 + anchors.bottom: parent.bottom + visible: !inputItem.disabled + color: { + if(FluTheme.dark){ + inputItem.activeFocus ? FluTheme.primaryColor.lighter : Qt.rgba(166/255,166/255,166/255,1) + }else{ + return inputItem.activeFocus ? FluTheme.primaryColor.dark : Qt.rgba(183/255,183/255,183/255,1) + } + } + Behavior on height{ + enabled: FluTheme.enableAnimation + NumberAnimation{ + duration: 83 + easing.type: Easing.OutCubic + } + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluTextBoxMenu.qml b/src/Qt5/imports/FluentUI/Controls/FluTextBoxMenu.qml new file mode 100644 index 00000000..6d5eedd8 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluTextBoxMenu.qml @@ -0,0 +1,51 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +FluMenu{ + property string cutText : "剪切" + property string copyText : "复制" + property string pasteText : "粘贴" + property string selectAllText : "全选" + property var inputItem + id:menu + enableAnimation: false + width: 120 + onVisibleChanged: { + inputItem.forceActiveFocus() + } + Connections{ + target: inputItem + function onTextChanged() { + menu.close() + } + } + FluMenuItem{ + text: cutText + visible: inputItem.selectedText !== "" && !inputItem.readOnly + onClicked: { + inputItem.cut() + } + } + FluMenuItem{ + text: copyText + visible: inputItem.selectedText !== "" + onClicked: { + inputItem.copy() + } + } + FluMenuItem{ + text: pasteText + visible: inputItem.canPaste + onClicked: { + inputItem.paste() + } + } + FluMenuItem{ + text: selectAllText + visible: inputItem.text !== "" + onClicked: { + inputItem.selectAll() + } + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluTextButton.qml b/src/Qt5/imports/FluentUI/Controls/FluTextButton.qml new file mode 100644 index 00000000..e61bdf39 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluTextButton.qml @@ -0,0 +1,64 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Button { + property bool disabled: false + property string contentDescription: "" + property color normalColor: FluTheme.dark ? FluTheme.primaryColor.lighter : FluTheme.primaryColor.dark + property color hoverColor: FluTheme.dark ? Qt.darker(normalColor,1.15) : Qt.lighter(normalColor,1.15) + property color pressedColor: FluTheme.dark ? Qt.darker(normalColor,1.3) : Qt.lighter(normalColor,1.3) + property color disableColor: FluTheme.dark ? Qt.rgba(82/255,82/255,82/255,1) : Qt.rgba(199/255,199/255,199/255,1) + property color backgroundHoverColor: FluTheme.dark ? Qt.rgba(1,1,1,0.03) : Qt.rgba(0,0,0,0.03) + property color backgroundPressedColor: FluTheme.dark ? Qt.rgba(1,1,1,0.06) : Qt.rgba(0,0,0,0.06) + property color backgroundNormalColor: FluTheme.dark ? Qt.rgba(0,0,0,0) : Qt.rgba(0,0,0,0) + property color backgroundDisableColor: FluTheme.dark ? Qt.rgba(0,0,0,0) : Qt.rgba(0,0,0,0) + property bool textBold: true + property color textColor: { + if(!enabled){ + return disableColor + } + if(pressed){ + return pressedColor + } + return hovered ? hoverColor :normalColor + } + id: control + horizontalPadding:6 + enabled: !disabled + font:FluTextStyle.Body + background: Rectangle{ + implicitWidth: 28 + implicitHeight: 28 + radius: 4 + color: { + if(!enabled){ + return backgroundDisableColor + } + if(pressed){ + return backgroundPressedColor + } + if(hovered){ + return backgroundHoverColor + } + return backgroundNormalColor + } + FluFocusRectangle{ + visible: control.visualFocus + radius:8 + } + } + focusPolicy:Qt.TabFocus + Accessible.role: Accessible.Button + Accessible.name: control.text + Accessible.description: contentDescription + Accessible.onPressAction: control.clicked() + contentItem: FluText { + id:btn_text + text: control.text + font: control.font + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: control.textColor + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluTimePicker.qml b/src/Qt5/imports/FluentUI/Controls/FluTimePicker.qml new file mode 100644 index 00000000..208db934 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluTimePicker.qml @@ -0,0 +1,400 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Window 2.15 +import FluentUI 1.0 + +Rectangle { + property color dividerColor: FluTheme.dark ? Qt.rgba(77/255,77/255,77/255,1) : Qt.rgba(239/255,239/255,239/255,1) + property color hoverColor: FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1) + property color normalColor: FluTheme.dark ? Qt.rgba(61/255,61/255,61/255,1) : Qt.rgba(254/255,254/255,254/255,1) + property int hourFormat: FluTimePickerType.H + property int isH: hourFormat === FluTimePickerType.H + property var current + id:control + color: { + if(mouse_area.containsMouse){ + return hoverColor + } + return normalColor + } + height: 30 + width: 300 + radius: 4 + border.width: 1 + border.color: dividerColor + Item{ + id:d + property var window: Window.window + property bool changeFlag: true + property var rowData: ["","",""] + visible: false + } + MouseArea{ + id:mouse_area + hoverEnabled: true + anchors.fill: parent + onClicked: { + popup.showPopup() + } + } + Rectangle{ + id:divider_1 + width: 1 + x: isH ? parent.width/3 : parent.width/2 + height: parent.height + color: dividerColor + } + Rectangle{ + id:divider_2 + width: 1 + x:parent.width*2/3 + height: parent.height + color: dividerColor + visible: isH + } + FluText{ + id:text_hour + anchors{ + left: parent.left + right: divider_1.left + top: parent.top + bottom: parent.bottom + } + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text:"时" + } + FluText{ + id:text_minute + anchors{ + left: divider_1.right + right: isH ? divider_2.left : parent.right + top: parent.top + bottom: parent.bottom + } + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text:"分" + } + FluText{ + id:text_ampm + visible: isH + anchors{ + left: divider_2.right + right: parent.right + top: parent.top + bottom: parent.bottom + } + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text:"AM/PM" + } + Menu{ + id:popup + width: container.width + height: container.height + modal: true + dim:false + enter: Transition { + reversible: true + NumberAnimation { + property: "opacity" + from:0 + to:1 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + exit:Transition { + NumberAnimation { + property: "opacity" + from:1 + to:0 + duration: FluTheme.enableAnimation ? 83 : 0 + } + } + background:Item{ + FluShadow{ + radius: 4 + } + } + contentItem: Item{ + clip: true + Rectangle{ + id:container + height: 340 + width: 300 + radius: 4 + color: FluTheme.dark ? Qt.rgba(51/255,48/255,48/255,1) : Qt.rgba(248/255,250/255,253/255,1) + MouseArea{ + anchors.fill: parent + } + RowLayout{ + id:layout_content + spacing: 0 + width: parent.width + height: 300 + Component{ + id:list_delegate + Item{ + height:38 + width:getListView().width + function getListView(){ + if(type === 0) + return list_view_1 + if(type === 1) + return list_view_2 + if(type === 2) + return list_view_3 + } + Rectangle{ + anchors.fill: parent + anchors.topMargin: 2 + anchors.bottomMargin: 2 + anchors.leftMargin: 5 + anchors.rightMargin: 5 + color: { + if(getListView().currentIndex === position){ + if(FluTheme.dark){ + return item_mouse.containsMouse ? Qt.darker(FluTheme.primaryColor.lighter,1.1) : FluTheme.primaryColor.lighter + }else{ + return item_mouse.containsMouse ? Qt.lighter(FluTheme.primaryColor.dark,1.1): FluTheme.primaryColor.dark + } + } + if(item_mouse.containsMouse){ + return FluTheme.dark ? Qt.rgba(63/255,60/255,61/255,1) : Qt.rgba(237/255,237/255,242/255,1) + } + return FluTheme.dark ? Qt.rgba(51/255,48/255,48/255,1) : Qt.rgba(0,0,0,0) + } + radius: 3 + MouseArea{ + id:item_mouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + getListView().currentIndex = position + if(type === 0){ + text_hour.text = model + } + if(type === 1){ + text_minute.text = model + } + if(type === 2){ + text_ampm.text = model + } + } + } + FluText{ + text:model + color: { + if(getListView().currentIndex === position){ + if(FluTheme.dark){ + return Qt.rgba(0,0,0,1) + }else{ + return Qt.rgba(1,1,1,1) + } + }else{ + return FluTheme.dark ? "#FFFFFF" : "#1A1A1A" + } + } + anchors.centerIn: parent + } + } + } + } + ListView{ + id:list_view_1 + width: isH ? 100 : 150 + height: parent.height + boundsBehavior:Flickable.StopAtBounds + ScrollBar.vertical: FluScrollBar {} + preferredHighlightBegin: 0 + preferredHighlightEnd: 0 + highlightMoveDuration: 0 + model: isH ? generateArray(1,12) : generateArray(0,23) + clip: true + delegate: Loader{ + property var model: modelData + property int type:0 + property int position:index + sourceComponent: list_delegate + } + } + Rectangle{ + width: 1 + height: parent.height + color: dividerColor + } + ListView{ + id:list_view_2 + width: isH ? 100 : 150 + height: parent.height + model: generateArray(0,59) + clip: true + preferredHighlightBegin: 0 + preferredHighlightEnd: 0 + highlightMoveDuration: 0 + ScrollBar.vertical: FluScrollBar {} + boundsBehavior:Flickable.StopAtBounds + delegate: Loader{ + property var model: modelData + property int type:1 + property int position:index + sourceComponent: list_delegate + } + } + Rectangle{ + width: 1 + height: parent.height + color: dividerColor + visible: isH + } + ListView{ + id:list_view_3 + width: 100 + height: 76 + model: ["上午","下午"] + clip: true + visible: isH + preferredHighlightBegin: 0 + preferredHighlightEnd: 0 + highlightMoveDuration: 0 + ScrollBar.vertical: FluScrollBar {} + Layout.alignment: Qt.AlignVCenter + boundsBehavior:Flickable.StopAtBounds + delegate: Loader{ + property var model: modelData + property int type:2 + property int position:index + sourceComponent: list_delegate + } + } + } + Rectangle{ + width: parent.width + height: 1 + anchors.top: layout_content.bottom + color: dividerColor + } + Rectangle{ + id:layout_actions + height: 40 + radius: 5 + color: FluTheme.dark ? Qt.rgba(32/255,32/255,32/255,1) : Qt.rgba(243/255,243/255,243/255,1) + anchors{ + bottom:parent.bottom + left: parent.left + right: parent.right + } + Item { + id:divider + width: 1 + height: parent.height + anchors.centerIn: parent + } + FluButton{ + anchors{ + left: parent.left + leftMargin: 20 + rightMargin: 10 + right: divider.left + verticalCenter: parent.verticalCenter + } + text: "取消" + onClicked: { + popup.close() + } + } + FluFilledButton{ + anchors{ + right: parent.right + left: divider.right + rightMargin: 20 + leftMargin: 10 + verticalCenter: parent.verticalCenter + } + text: "确定" + onClicked: { + d.changeFlag = false + popup.close() + const hours = text_hour.text + const minutes = text_minute.text + const period = text_ampm.text + const date = new Date() + var hours24 = parseInt(hours); + if(control.hourFormat === FluTimePickerType.H){ + if (hours === "12") { + hours24 = (period === "上午") ? 0 : 12; + } else { + hours24 = (period === "上午") ? hours24 : hours24 + 12; + } + } + date.setHours(hours24); + date.setMinutes(parseInt(minutes)); + date.setSeconds(0); + current = date + } + } + } + } + } + y:35 + function showPopup() { + d.changeFlag = true + d.rowData[0] = text_hour.text + d.rowData[1] = text_minute.text + d.rowData[2] = text_ampm.text + + var now = new Date(); + + var hour + var ampm; + if(isH){ + hour = now.getHours(); + if(hour>12){ + ampm = "下午" + hour = hour-12 + }else{ + ampm = "上午" + } + }else{ + hour = now.getHours(); + } + hour = text_hour.text === "时"? hour.toString().padStart(2, '0') : text_hour.text + var minute = text_minute.text === "分"? now.getMinutes().toString().padStart(2, '0') : text_minute.text + ampm = text_ampm.text === "AM/PM"?ampm:text_ampm.text + list_view_1.currentIndex = list_view_1.model.indexOf(hour); + list_view_2.currentIndex = list_view_2.model.indexOf(minute); + list_view_3.currentIndex = list_view_3.model.indexOf(ampm); + + text_hour.text = hour + text_minute.text = minute + if(isH){ + text_ampm.text = ampm + } + var pos = control.mapToItem(null, 0, 0) + if(d.window.height>pos.y+control.height+container.height){ + popup.y = control.height + } else if(pos.y>container.height){ + popup.y = -container.height + } else { + popup.y = d.window.height-(pos.y+container.height) + } + popup.open() + } + onClosed: { + if(d.changeFlag){ + text_hour.text = d.rowData[0] + text_minute.text = d.rowData[1] + text_ampm.text = d.rowData[2] + } + } + } + function generateArray(start, n) { + var arr = []; + for (var i = start; i <= n; i++) { + arr.push(i.toString().padStart(2, '0')); + } + return arr; + } +} diff --git a/src/Qt5/imports/FluentUI/Controls/FluTimeline.qml b/src/Qt5/imports/FluentUI/Controls/FluTimeline.qml new file mode 100644 index 00000000..8d0863a6 --- /dev/null +++ b/src/Qt5/imports/FluentUI/Controls/FluTimeline.qml @@ -0,0 +1,294 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import FluentUI 1.0 + +Item{ + property int mode: FluTimelineType.Left + property alias model: repeater.model + property color lineColor: FluTheme.dark ? Qt.rgba(80/255,80/255,80/255,1) : Qt.rgba(210/255,210/255,210/255,1) + id:control + implicitWidth: 380 + implicitHeight: layout_column.height + QtObject{ + id:d + property bool isLeft: control.mode === FluTimelineType.Left + property bool isRight: control.mode === FluTimelineType.Right + property bool isAlternate: control.mode === FluTimelineType.Alternate + property bool hasLable: { + for(var i=0;i 0) { + const curr = stack.pop(); + result.unshift(curr); + if (curr.items) { + for(var i=0 ; i 0) { + const curr = stack.pop(); + if (curr.items) { + for(var i=0 ; i 0) { + const curr = stack.pop(); + if(curr.multipSelected){ + result.push(curr) + } + + for(var i=0 ; i 1) { + h -= 1; + } + } + + return [ + h * 360, + s * 100, + v * 100 + ]; +}; + +convert.rgb.hwb = function (rgb) { + var r = rgb[0]; + var g = rgb[1]; + var b = rgb[2]; + var h = convert.rgb.hsl(rgb)[0]; + var w = 1 / 255 * Math.min(r, Math.min(g, b)); + + b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); + + return [h, w * 100, b * 100]; +}; + +convert.rgb.cmyk = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var c; + var m; + var y; + var k; + + k = Math.min(1 - r, 1 - g, 1 - b); + c = (1 - r - k) / (1 - k) || 0; + m = (1 - g - k) / (1 - k) || 0; + y = (1 - b - k) / (1 - k) || 0; + + return [c * 100, m * 100, y * 100, k * 100]; +}; + +/** + * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance + * */ +function comparativeDistance(x, y) { + return ( + Math.pow(x[0] - y[0], 2) + + Math.pow(x[1] - y[1], 2) + + Math.pow(x[2] - y[2], 2) + ); +} + +convert.rgb.keyword = function (rgb) { + var reversed = reverseKeywords[rgb]; + if (reversed) { + return reversed; + } + + var currentClosestDistance = Infinity; + var currentClosestKeyword; + + for (var keyword in colorName) { + if (colorName.hasOwnProperty(keyword)) { + var value = colorName[keyword]; + + // Compute comparative distance + var distance = comparativeDistance(rgb, value); + + // Check if its less, if so set as closest + if (distance < currentClosestDistance) { + currentClosestDistance = distance; + currentClosestKeyword = keyword; + } + } + } + + return currentClosestKeyword; +}; + +convert.keyword.rgb = function (keyword) { + return colorName[keyword]; +}; + +convert.rgb.xyz = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + + // assume sRGB + r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); + g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); + b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); + + var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + + return [x * 100, y * 100, z * 100]; +}; + +convert.rgb.lab = function (rgb) { + var xyz = convert.rgb.xyz(rgb); + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +}; + +convert.hsl.rgb = function (hsl) { + var h = hsl[0] / 360; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var t1; + var t2; + var t3; + var rgb; + var val; + + if (s === 0) { + val = l * 255; + return [val, val, val]; + } + + if (l < 0.5) { + t2 = l * (1 + s); + } else { + t2 = l + s - l * s; + } + + t1 = 2 * l - t2; + + rgb = [0, 0, 0]; + for (var i = 0; i < 3; i++) { + t3 = h + 1 / 3 * -(i - 1); + if (t3 < 0) { + t3++; + } + if (t3 > 1) { + t3--; + } + + if (6 * t3 < 1) { + val = t1 + (t2 - t1) * 6 * t3; + } else if (2 * t3 < 1) { + val = t2; + } else if (3 * t3 < 2) { + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + } else { + val = t1; + } + + rgb[i] = val * 255; + } + + return rgb; +}; + +convert.hsl.hsv = function (hsl) { + var h = hsl[0]; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var smin = s; + var lmin = Math.max(l, 0.01); + var sv; + var v; + + l *= 2; + s *= (l <= 1) ? l : 2 - l; + smin *= lmin <= 1 ? lmin : 2 - lmin; + v = (l + s) / 2; + sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); + + return [h, sv * 100, v * 100]; +}; + +convert.hsv.rgb = function (hsv) { + var h = hsv[0] / 60; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var hi = Math.floor(h) % 6; + + var f = h - Math.floor(h); + var p = 255 * v * (1 - s); + var q = 255 * v * (1 - (s * f)); + var t = 255 * v * (1 - (s * (1 - f))); + v *= 255; + + switch (hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } +}; + +convert.hsv.hsl = function (hsv) { + var h = hsv[0]; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var vmin = Math.max(v, 0.01); + var lmin; + var sl; + var l; + + l = (2 - s) * v; + lmin = (2 - s) * vmin; + sl = s * vmin; + sl /= (lmin <= 1) ? lmin : 2 - lmin; + sl = sl || 0; + l /= 2; + + return [h, sl * 100, l * 100]; +}; + +// http://dev.w3.org/csswg/css-color/#hwb-to-rgb +convert.hwb.rgb = function (hwb) { + var h = hwb[0] / 360; + var wh = hwb[1] / 100; + var bl = hwb[2] / 100; + var ratio = wh + bl; + var i; + var v; + var f; + var n; + + // wh + bl cant be > 1 + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + + i = Math.floor(6 * h); + v = 1 - bl; + f = 6 * h - i; + + if ((i & 0x01) !== 0) { + f = 1 - f; + } + + n = wh + f * (v - wh); // linear interpolation + + var r; + var g; + var b; + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + + return [r * 255, g * 255, b * 255]; +}; + +convert.cmyk.rgb = function (cmyk) { + var c = cmyk[0] / 100; + var m = cmyk[1] / 100; + var y = cmyk[2] / 100; + var k = cmyk[3] / 100; + var r; + var g; + var b; + + r = 1 - Math.min(1, c * (1 - k) + k); + g = 1 - Math.min(1, m * (1 - k) + k); + b = 1 - Math.min(1, y * (1 - k) + k); + + return [r * 255, g * 255, b * 255]; +}; + +convert.xyz.rgb = function (xyz) { + var x = xyz[0] / 100; + var y = xyz[1] / 100; + var z = xyz[2] / 100; + var r; + var g; + var b; + + r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); + + // assume sRGB + r = r > 0.0031308 + ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) + : r * 12.92; + + g = g > 0.0031308 + ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) + : g * 12.92; + + b = b > 0.0031308 + ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) + : b * 12.92; + + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); + + return [r * 255, g * 255, b * 255]; +}; + +convert.xyz.lab = function (xyz) { + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +}; + +convert.lab.xyz = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var x; + var y; + var z; + + y = (l + 16) / 116; + x = a / 500 + y; + z = y - b / 200; + + var y2 = Math.pow(y, 3); + var x2 = Math.pow(x, 3); + var z2 = Math.pow(z, 3); + y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; + x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; + z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; + + x *= 95.047; + y *= 100; + z *= 108.883; + + return [x, y, z]; +}; + +convert.lab.lch = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var hr; + var h; + var c; + + hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + + if (h < 0) { + h += 360; + } + + c = Math.sqrt(a * a + b * b); + + return [l, c, h]; +}; + +convert.lch.lab = function (lch) { + var l = lch[0]; + var c = lch[1]; + var h = lch[2]; + var a; + var b; + var hr; + + hr = h / 360 * 2 * Math.PI; + a = c * Math.cos(hr); + b = c * Math.sin(hr); + + return [l, a, b]; +}; + +convert.rgb.ansi16 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization + + value = Math.round(value / 50); + + if (value === 0) { + return 30; + } + + var ansi = 30 + + ((Math.round(b / 255) << 2) + | (Math.round(g / 255) << 1) + | Math.round(r / 255)); + + if (value === 2) { + ansi += 60; + } + + return ansi; +}; + +convert.hsv.ansi16 = function (args) { + // optimization here; we already know the value and don't need to get + // it converted for us. + return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); +}; + +convert.rgb.ansi256 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + + // we use the extended greyscale palette here, with the exception of + // black and white. normal palette only has 4 greyscale shades. + if (r === g && g === b) { + if (r < 8) { + return 16; + } + + if (r > 248) { + return 231; + } + + return Math.round(((r - 8) / 247) * 24) + 232; + } + + var ansi = 16 + + (36 * Math.round(r / 255 * 5)) + + (6 * Math.round(g / 255 * 5)) + + Math.round(b / 255 * 5); + + return ansi; +}; + +convert.ansi16.rgb = function (args) { + var color = args % 10; + + // handle greyscale + if (color === 0 || color === 7) { + if (args > 50) { + color += 3.5; + } + + color = color / 10.5 * 255; + + return [color, color, color]; + } + + var mult = (~~(args > 50) + 1) * 0.5; + var r = ((color & 1) * mult) * 255; + var g = (((color >> 1) & 1) * mult) * 255; + var b = (((color >> 2) & 1) * mult) * 255; + + return [r, g, b]; +}; + +convert.ansi256.rgb = function (args) { + // handle greyscale + if (args >= 232) { + var c = (args - 232) * 10 + 8; + return [c, c, c]; + } + + args -= 16; + + var rem; + var r = Math.floor(args / 36) / 5 * 255; + var g = Math.floor((rem = args % 36) / 6) / 5 * 255; + var b = (rem % 6) / 5 * 255; + + return [r, g, b]; +}; + +convert.rgb.hex = function (args) { + var integer = ((Math.round(args[0]) & 0xFF) << 16) + + ((Math.round(args[1]) & 0xFF) << 8) + + (Math.round(args[2]) & 0xFF); + + var string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; + +convert.hex.rgb = function (args) { + var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); + if (!match) { + return [0, 0, 0]; + } + + var colorString = match[0]; + + if (match[0].length === 3) { + colorString = colorString.split('').map(function (char) { + return char + char; + }).join(''); + } + + var integer = parseInt(colorString, 16); + var r = (integer >> 16) & 0xFF; + var g = (integer >> 8) & 0xFF; + var b = integer & 0xFF; + + return [r, g, b]; +}; + +convert.rgb.hcg = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var max = Math.max(Math.max(r, g), b); + var min = Math.min(Math.min(r, g), b); + var chroma = (max - min); + var grayscale; + var hue; + + if (chroma < 1) { + grayscale = min / (1 - chroma); + } else { + grayscale = 0; + } + + if (chroma <= 0) { + hue = 0; + } else + if (max === r) { + hue = ((g - b) / chroma) % 6; + } else + if (max === g) { + hue = 2 + (b - r) / chroma; + } else { + hue = 4 + (r - g) / chroma + 4; + } + + hue /= 6; + hue %= 1; + + return [hue * 360, chroma * 100, grayscale * 100]; +}; + +convert.hsl.hcg = function (hsl) { + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var c = 1; + var f = 0; + + if (l < 0.5) { + c = 2.0 * s * l; + } else { + c = 2.0 * s * (1.0 - l); + } + + if (c < 1.0) { + f = (l - 0.5 * c) / (1.0 - c); + } + + return [hsl[0], c * 100, f * 100]; +}; + +convert.hsv.hcg = function (hsv) { + var s = hsv[1] / 100; + var v = hsv[2] / 100; + + var c = s * v; + var f = 0; + + if (c < 1.0) { + f = (v - c) / (1 - c); + } + + return [hsv[0], c * 100, f * 100]; +}; + +convert.hcg.rgb = function (hcg) { + var h = hcg[0] / 360; + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + if (c === 0.0) { + return [g * 255, g * 255, g * 255]; + } + + var pure = [0, 0, 0]; + var hi = (h % 1) * 6; + var v = hi % 1; + var w = 1 - v; + var mg = 0; + + switch (Math.floor(hi)) { + case 0: + pure[0] = 1; pure[1] = v; pure[2] = 0; break; + case 1: + pure[0] = w; pure[1] = 1; pure[2] = 0; break; + case 2: + pure[0] = 0; pure[1] = 1; pure[2] = v; break; + case 3: + pure[0] = 0; pure[1] = w; pure[2] = 1; break; + case 4: + pure[0] = v; pure[1] = 0; pure[2] = 1; break; + default: + pure[0] = 1; pure[1] = 0; pure[2] = w; + } + + mg = (1.0 - c) * g; + + return [ + (c * pure[0] + mg) * 255, + (c * pure[1] + mg) * 255, + (c * pure[2] + mg) * 255 + ]; +}; + +convert.hcg.hsv = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + var v = c + g * (1.0 - c); + var f = 0; + + if (v > 0.0) { + f = c / v; + } + + return [hcg[0], f * 100, v * 100]; +}; + +convert.hcg.hsl = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + var l = g * (1.0 - c) + 0.5 * c; + var s = 0; + + if (l > 0.0 && l < 0.5) { + s = c / (2 * l); + } else + if (l >= 0.5 && l < 1.0) { + s = c / (2 * (1 - l)); + } + + return [hcg[0], s * 100, l * 100]; +}; + +convert.hcg.hwb = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + var v = c + g * (1.0 - c); + return [hcg[0], (v - c) * 100, (1 - v) * 100]; +}; + +convert.hwb.hcg = function (hwb) { + var w = hwb[1] / 100; + var b = hwb[2] / 100; + var v = 1 - b; + var c = v - w; + var g = 0; + + if (c < 1) { + g = (v - c) / (1 - c); + } + + return [hwb[0], c * 100, g * 100]; +}; + +convert.apple.rgb = function (apple) { + return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; +}; + +convert.rgb.apple = function (rgb) { + return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; +}; + +convert.gray.rgb = function (args) { + return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; +}; + +convert.gray.hsl = convert.gray.hsv = function (args) { + return [0, 0, args[0]]; +}; + +convert.gray.hwb = function (gray) { + return [0, 100, gray[0]]; +}; + +convert.gray.cmyk = function (gray) { + return [0, 0, 0, gray[0]]; +}; + +convert.gray.lab = function (gray) { + return [gray[0], 0, 0]; +}; + +convert.gray.hex = function (gray) { + var val = Math.round(gray[0] / 100 * 255) & 0xFF; + var integer = (val << 16) + (val << 8) + val; + + var string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; + +convert.rgb.gray = function (rgb) { + var val = (rgb[0] + rgb[1] + rgb[2]) / 3; + return [val / 255 * 100]; +}; +}); +var conversions_1 = conversions.rgb; +var conversions_2 = conversions.hsl; +var conversions_3 = conversions.hsv; +var conversions_4 = conversions.hwb; +var conversions_5 = conversions.cmyk; +var conversions_6 = conversions.xyz; +var conversions_7 = conversions.lab; +var conversions_8 = conversions.lch; +var conversions_9 = conversions.hex; +var conversions_10 = conversions.keyword; +var conversions_11 = conversions.ansi16; +var conversions_12 = conversions.ansi256; +var conversions_13 = conversions.hcg; +var conversions_14 = conversions.apple; +var conversions_15 = conversions.gray; + +/* + this function routes a model to all other models. + + all functions that are routed have a property `.conversion` attached + to the returned synthetic function. This property is an array + of strings, each with the steps in between the 'from' and 'to' + color models (inclusive). + + conversions that are not possible simply are not included. +*/ + +function buildGraph() { + var graph = {}; + // https://jsperf.com/object-keys-vs-for-in-with-closure/3 + var models = Object.keys(conversions); + + for (var len = models.length, i = 0; i < len; i++) { + graph[models[i]] = { + // http://jsperf.com/1-vs-infinity + // micro-opt, but this is simple. + distance: -1, + parent: null + }; + } + + return graph; +} + +// https://en.wikipedia.org/wiki/Breadth-first_search +function deriveBFS(fromModel) { + var graph = buildGraph(); + var queue = [fromModel]; // unshift -> queue -> pop + + graph[fromModel].distance = 0; + + while (queue.length) { + var current = queue.pop(); + var adjacents = Object.keys(conversions[current]); + + for (var len = adjacents.length, i = 0; i < len; i++) { + var adjacent = adjacents[i]; + var node = graph[adjacent]; + + if (node.distance === -1) { + node.distance = graph[current].distance + 1; + node.parent = current; + queue.unshift(adjacent); + } + } + } + + return graph; +} + +function link(from, to) { + return function (args) { + return to(from(args)); + }; +} + +function wrapConversion(toModel, graph) { + var path = [graph[toModel].parent, toModel]; + var fn = conversions[graph[toModel].parent][toModel]; + + var cur = graph[toModel].parent; + while (graph[cur].parent) { + path.unshift(graph[cur].parent); + fn = link(conversions[graph[cur].parent][cur], fn); + cur = graph[cur].parent; + } + + fn.conversion = path; + return fn; +} + +var route = function (fromModel) { + var graph = deriveBFS(fromModel); + var conversion = {}; + + var models = Object.keys(graph); + for (var len = models.length, i = 0; i < len; i++) { + var toModel = models[i]; + var node = graph[toModel]; + + if (node.parent === null) { + // no possible conversion, or this node is the source model. + continue; + } + + conversion[toModel] = wrapConversion(toModel, graph); + } + + return conversion; +}; + +var convert = {}; + +var models = Object.keys(conversions); + +function wrapRaw(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } + + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } + + return fn(args); + }; + + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; +} + +function wrapRounded(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } + + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } + + var result = fn(args); + + // we're assuming the result is an array here. + // see notice in conversions.js; don't use box types + // in conversion functions. + if (typeof result === 'object') { + for (var len = result.length, i = 0; i < len; i++) { + result[i] = Math.round(result[i]); + } + } + + return result; + }; + + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; +} + +models.forEach(function (fromModel) { + convert[fromModel] = {}; + + Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); + Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); + + var routes = route(fromModel); + var routeModels = Object.keys(routes); + + routeModels.forEach(function (toModel) { + var fn = routes[toModel]; + + convert[fromModel][toModel] = wrapRounded(fn); + convert[fromModel][toModel].raw = wrapRaw(fn); + }); +}); + +var colorConvert = convert; + +var colorName$1 = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; + +/* MIT license */ + + +var colorString = { + getRgba: getRgba, + getHsla: getHsla, + getRgb: getRgb, + getHsl: getHsl, + getHwb: getHwb, + getAlpha: getAlpha, + + hexString: hexString, + rgbString: rgbString, + rgbaString: rgbaString, + percentString: percentString, + percentaString: percentaString, + hslString: hslString, + hslaString: hslaString, + hwbString: hwbString, + keyword: keyword +}; + +function getRgba(string) { + if (!string) { + return; + } + var abbr = /^#([a-fA-F0-9]{3,4})$/i, + hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i, + rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, + per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, + keyword = /(\w+)/; + + var rgb = [0, 0, 0], + a = 1, + match = string.match(abbr), + hexAlpha = ""; + if (match) { + match = match[1]; + hexAlpha = match[3]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i] + match[i], 16); + } + if (hexAlpha) { + a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100; + } + } + else if (match = string.match(hex)) { + hexAlpha = match[2]; + match = match[1]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); + } + if (hexAlpha) { + a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100; + } + } + else if (match = string.match(rgba)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i + 1]); + } + a = parseFloat(match[4]); + } + else if (match = string.match(per)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); + } + a = parseFloat(match[4]); + } + else if (match = string.match(keyword)) { + if (match[1] == "transparent") { + return [0, 0, 0, 0]; + } + rgb = colorName$1[match[1]]; + if (!rgb) { + return; + } + } + + for (var i = 0; i < rgb.length; i++) { + rgb[i] = scale(rgb[i], 0, 255); + } + if (!a && a != 0) { + a = 1; + } + else { + a = scale(a, 0, 1); + } + rgb[3] = a; + return rgb; +} + +function getHsla(string) { + if (!string) { + return; + } + var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hsl); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + s = scale(parseFloat(match[2]), 0, 100), + l = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, s, l, a]; + } +} + +function getHwb(string) { + if (!string) { + return; + } + var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hwb); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + w = scale(parseFloat(match[2]), 0, 100), + b = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, w, b, a]; + } +} + +function getRgb(string) { + var rgba = getRgba(string); + return rgba && rgba.slice(0, 3); +} + +function getHsl(string) { + var hsla = getHsla(string); + return hsla && hsla.slice(0, 3); +} + +function getAlpha(string) { + var vals = getRgba(string); + if (vals) { + return vals[3]; + } + else if (vals = getHsla(string)) { + return vals[3]; + } + else if (vals = getHwb(string)) { + return vals[3]; + } +} + +// generators +function hexString(rgba, a) { + var a = (a !== undefined && rgba.length === 3) ? a : rgba[3]; + return "#" + hexDouble(rgba[0]) + + hexDouble(rgba[1]) + + hexDouble(rgba[2]) + + ( + (a >= 0 && a < 1) + ? hexDouble(Math.round(a * 255)) + : "" + ); +} + +function rgbString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return rgbaString(rgba, alpha); + } + return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; +} + +function rgbaString(rgba, alpha) { + if (alpha === undefined) { + alpha = (rgba[3] !== undefined ? rgba[3] : 1); + } + return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + + ", " + alpha + ")"; +} + +function percentString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return percentaString(rgba, alpha); + } + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + + return "rgb(" + r + "%, " + g + "%, " + b + "%)"; +} + +function percentaString(rgba, alpha) { + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; +} + +function hslString(hsla, alpha) { + if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { + return hslaString(hsla, alpha); + } + return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; +} + +function hslaString(hsla, alpha) { + if (alpha === undefined) { + alpha = (hsla[3] !== undefined ? hsla[3] : 1); + } + return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + + alpha + ")"; +} + +// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax +// (hwb have alpha optional & 1 is default value) +function hwbString(hwb, alpha) { + if (alpha === undefined) { + alpha = (hwb[3] !== undefined ? hwb[3] : 1); + } + return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" + + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; +} + +// helpers +function scale(num, min, max) { + return Math.min(Math.max(min, num), max); +} + +function hexDouble(num) { + var str = num.toString(16).toUpperCase(); + return (str.length < 2) ? "0" + str : str; +} + +//create a list of reverse color names +var reverseNames = {}; +for (var name in colorName$1) { + reverseNames[colorName$1[name]] = name; +} + +function keyword(rgb) { + return reverseNames[rgb.slice(0, 3)]; +} + +var Color = function (obj) { + if (obj instanceof Color) { + return obj; + } + if (!(this instanceof Color)) { + return new Color(obj); + } + + this.valid = false; + this.values = { + rgb: [0, 0, 0], + hsl: [0, 0, 0], + hsv: [0, 0, 0], + hwb: [0, 0, 0], + cmyk: [0, 0, 0, 0], + alpha: 1 + }; + + // parse Color() argument + var vals; + if (typeof obj === 'string') { + vals = colorString.getRgba(obj); + if (vals) { + this.setValues('rgb', vals); + } else if (vals = colorString.getHsla(obj)) { + this.setValues('hsl', vals); + } else if (vals = colorString.getHwb(obj)) { + this.setValues('hwb', vals); + } + } else if (typeof obj === 'object') { + vals = obj; + if (vals.r !== undefined || vals.red !== undefined) { + this.setValues('rgb', vals); + } else if (vals.l !== undefined || vals.lightness !== undefined) { + this.setValues('hsl', vals); + } else if (vals.v !== undefined || vals.value !== undefined) { + this.setValues('hsv', vals); + } else if (vals.w !== undefined || vals.whiteness !== undefined) { + this.setValues('hwb', vals); + } else if (vals.c !== undefined || vals.cyan !== undefined) { + this.setValues('cmyk', vals); + } + } +}; + +Color.prototype = { + isValid: function () { + return this.valid; + }, + rgb: function () { + return this.setSpace('rgb', arguments); + }, + hsl: function () { + return this.setSpace('hsl', arguments); + }, + hsv: function () { + return this.setSpace('hsv', arguments); + }, + hwb: function () { + return this.setSpace('hwb', arguments); + }, + cmyk: function () { + return this.setSpace('cmyk', arguments); + }, + + rgbArray: function () { + return this.values.rgb; + }, + hslArray: function () { + return this.values.hsl; + }, + hsvArray: function () { + return this.values.hsv; + }, + hwbArray: function () { + var values = this.values; + if (values.alpha !== 1) { + return values.hwb.concat([values.alpha]); + } + return values.hwb; + }, + cmykArray: function () { + return this.values.cmyk; + }, + rgbaArray: function () { + var values = this.values; + return values.rgb.concat([values.alpha]); + }, + hslaArray: function () { + var values = this.values; + return values.hsl.concat([values.alpha]); + }, + alpha: function (val) { + if (val === undefined) { + return this.values.alpha; + } + this.setValues('alpha', val); + return this; + }, + + red: function (val) { + return this.setChannel('rgb', 0, val); + }, + green: function (val) { + return this.setChannel('rgb', 1, val); + }, + blue: function (val) { + return this.setChannel('rgb', 2, val); + }, + hue: function (val) { + if (val) { + val %= 360; + val = val < 0 ? 360 + val : val; + } + return this.setChannel('hsl', 0, val); + }, + saturation: function (val) { + return this.setChannel('hsl', 1, val); + }, + lightness: function (val) { + return this.setChannel('hsl', 2, val); + }, + saturationv: function (val) { + return this.setChannel('hsv', 1, val); + }, + whiteness: function (val) { + return this.setChannel('hwb', 1, val); + }, + blackness: function (val) { + return this.setChannel('hwb', 2, val); + }, + value: function (val) { + return this.setChannel('hsv', 2, val); + }, + cyan: function (val) { + return this.setChannel('cmyk', 0, val); + }, + magenta: function (val) { + return this.setChannel('cmyk', 1, val); + }, + yellow: function (val) { + return this.setChannel('cmyk', 2, val); + }, + black: function (val) { + return this.setChannel('cmyk', 3, val); + }, + + hexString: function () { + return colorString.hexString(this.values.rgb); + }, + rgbString: function () { + return colorString.rgbString(this.values.rgb, this.values.alpha); + }, + rgbaString: function () { + return colorString.rgbaString(this.values.rgb, this.values.alpha); + }, + percentString: function () { + return colorString.percentString(this.values.rgb, this.values.alpha); + }, + hslString: function () { + return colorString.hslString(this.values.hsl, this.values.alpha); + }, + hslaString: function () { + return colorString.hslaString(this.values.hsl, this.values.alpha); + }, + hwbString: function () { + return colorString.hwbString(this.values.hwb, this.values.alpha); + }, + keyword: function () { + return colorString.keyword(this.values.rgb, this.values.alpha); + }, + + rgbNumber: function () { + var rgb = this.values.rgb; + return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + }, + + luminosity: function () { + // http://www.w3.org/TR/WCAG20/#relativeluminancedef + var rgb = this.values.rgb; + var lum = []; + for (var i = 0; i < rgb.length; i++) { + var chan = rgb[i] / 255; + lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); + } + return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; + }, + + contrast: function (color2) { + // http://www.w3.org/TR/WCAG20/#contrast-ratiodef + var lum1 = this.luminosity(); + var lum2 = color2.luminosity(); + if (lum1 > lum2) { + return (lum1 + 0.05) / (lum2 + 0.05); + } + return (lum2 + 0.05) / (lum1 + 0.05); + }, + + level: function (color2) { + var contrastRatio = this.contrast(color2); + if (contrastRatio >= 7.1) { + return 'AAA'; + } + + return (contrastRatio >= 4.5) ? 'AA' : ''; + }, + + dark: function () { + // YIQ equation from http://24ways.org/2010/calculating-color-contrast + var rgb = this.values.rgb; + var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; + return yiq < 128; + }, + + light: function () { + return !this.dark(); + }, + + negate: function () { + var rgb = []; + for (var i = 0; i < 3; i++) { + rgb[i] = 255 - this.values.rgb[i]; + } + this.setValues('rgb', rgb); + return this; + }, + + lighten: function (ratio) { + var hsl = this.values.hsl; + hsl[2] += hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + darken: function (ratio) { + var hsl = this.values.hsl; + hsl[2] -= hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + saturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] += hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + desaturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] -= hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + whiten: function (ratio) { + var hwb = this.values.hwb; + hwb[1] += hwb[1] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + blacken: function (ratio) { + var hwb = this.values.hwb; + hwb[2] += hwb[2] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + greyscale: function () { + var rgb = this.values.rgb; + // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale + var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; + this.setValues('rgb', [val, val, val]); + return this; + }, + + clearer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha - (alpha * ratio)); + return this; + }, + + opaquer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha + (alpha * ratio)); + return this; + }, + + rotate: function (degrees) { + var hsl = this.values.hsl; + var hue = (hsl[0] + degrees) % 360; + hsl[0] = hue < 0 ? 360 + hue : hue; + this.setValues('hsl', hsl); + return this; + }, + + /** + * Ported from sass implementation in C + * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 + */ + mix: function (mixinColor, weight) { + var color1 = this; + var color2 = mixinColor; + var p = weight === undefined ? 0.5 : weight; + + var w = 2 * p - 1; + var a = color1.alpha() - color2.alpha(); + + var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + return this + .rgb( + w1 * color1.red() + w2 * color2.red(), + w1 * color1.green() + w2 * color2.green(), + w1 * color1.blue() + w2 * color2.blue() + ) + .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); + }, + + toJSON: function () { + return this.rgb(); + }, + + clone: function () { + // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, + // making the final build way to big to embed in Chart.js. So let's do it manually, + // assuming that values to clone are 1 dimension arrays containing only numbers, + // except 'alpha' which is a number. + var result = new Color(); + var source = this.values; + var target = result.values; + var value, type; + + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + value = source[prop]; + type = ({}).toString.call(value); + if (type === '[object Array]') { + target[prop] = value.slice(0); + } else if (type === '[object Number]') { + target[prop] = value; + } else { + console.error('unexpected color value:', value); + } + } + } + + return result; + } +}; + +Color.prototype.spaces = { + rgb: ['red', 'green', 'blue'], + hsl: ['hue', 'saturation', 'lightness'], + hsv: ['hue', 'saturation', 'value'], + hwb: ['hue', 'whiteness', 'blackness'], + cmyk: ['cyan', 'magenta', 'yellow', 'black'] +}; + +Color.prototype.maxes = { + rgb: [255, 255, 255], + hsl: [360, 100, 100], + hsv: [360, 100, 100], + hwb: [360, 100, 100], + cmyk: [100, 100, 100, 100] +}; + +Color.prototype.getValues = function (space) { + var values = this.values; + var vals = {}; + + for (var i = 0; i < space.length; i++) { + vals[space.charAt(i)] = values[space][i]; + } + + if (values.alpha !== 1) { + vals.a = values.alpha; + } + + // {r: 255, g: 255, b: 255, a: 0.4} + return vals; +}; + +Color.prototype.setValues = function (space, vals) { + var values = this.values; + var spaces = this.spaces; + var maxes = this.maxes; + var alpha = 1; + var i; + + this.valid = true; + + if (space === 'alpha') { + alpha = vals; + } else if (vals.length) { + // [10, 10, 10] + values[space] = vals.slice(0, space.length); + alpha = vals[space.length]; + } else if (vals[space.charAt(0)] !== undefined) { + // {r: 10, g: 10, b: 10} + for (i = 0; i < space.length; i++) { + values[space][i] = vals[space.charAt(i)]; + } + + alpha = vals.a; + } else if (vals[spaces[space][0]] !== undefined) { + // {red: 10, green: 10, blue: 10} + var chans = spaces[space]; + + for (i = 0; i < space.length; i++) { + values[space][i] = vals[chans[i]]; + } + + alpha = vals.alpha; + } + + values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); + + if (space === 'alpha') { + return false; + } + + var capped; + + // cap values of the space prior converting all values + for (i = 0; i < space.length; i++) { + capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); + values[space][i] = Math.round(capped); + } + + // convert to all the other color spaces + for (var sname in spaces) { + if (sname !== space) { + values[sname] = colorConvert[space][sname](values[space]); + } + } + + return true; +}; + +Color.prototype.setSpace = function (space, args) { + var vals = args[0]; + + if (vals === undefined) { + // color.rgb() + return this.getValues(space); + } + + // color.rgb(10, 10, 10) + if (typeof vals === 'number') { + vals = Array.prototype.slice.call(args); + } + + this.setValues(space, vals); + return this; +}; + +Color.prototype.setChannel = function (space, index, val) { + var svalues = this.values[space]; + if (val === undefined) { + // color.red() + return svalues[index]; + } else if (val === svalues[index]) { + // color.red(color.red()) + return this; + } + + // color.red(100) + svalues[index] = val; + this.setValues(space, svalues); + + return this; +}; + +if (typeof window !== 'undefined') { +// window.Color = Color; +} + +var chartjsColor = Color; + +/** + * @namespace Chart.helpers + */ +var helpers = { + /** + * An empty function that can be used, for example, for optional callback. + */ + noop: function() {}, + + /** + * Returns a unique id, sequentially generated from a global variable. + * @returns {number} + * @function + */ + uid: (function() { + var id = 0; + return function() { + return id++; + }; + }()), + + /** + * Returns true if `value` is neither null nor undefined, else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ + isNullOrUndef: function(value) { + return value === null || typeof value === 'undefined'; + }, + + /** + * Returns true if `value` is an array (including typed arrays), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @function + */ + isArray: function(value) { + if (Array.isArray && Array.isArray(value)) { + return true; + } + var type = Object.prototype.toString.call(value); + if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { + return true; + } + return false; + }, + + /** + * Returns true if `value` is an object (excluding null), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ + isObject: function(value) { + return value !== null && Object.prototype.toString.call(value) === '[object Object]'; + }, + + /** + * Returns true if `value` is a finite number, else returns false + * @param {*} value - The value to test. + * @returns {boolean} + */ + isFinite: function(value) { + return (typeof value === 'number' || value instanceof Number) && isFinite(value); + }, + + /** + * Returns `value` if defined, else returns `defaultValue`. + * @param {*} value - The value to return if defined. + * @param {*} defaultValue - The value to return if `value` is undefined. + * @returns {*} + */ + valueOrDefault: function(value, defaultValue) { + return typeof value === 'undefined' ? defaultValue : value; + }, + + /** + * Returns value at the given `index` in array if defined, else returns `defaultValue`. + * @param {Array} value - The array to lookup for value at `index`. + * @param {number} index - The index in `value` to lookup for value. + * @param {*} defaultValue - The value to return if `value[index]` is undefined. + * @returns {*} + */ + valueAtIndexOrDefault: function(value, index, defaultValue) { + return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); + }, + + /** + * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the + * value returned by `fn`. If `fn` is not a function, this method returns undefined. + * @param {function} fn - The function to call. + * @param {Array|undefined|null} args - The arguments with which `fn` should be called. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @returns {*} + */ + callback: function(fn, args, thisArg) { + if (fn && typeof fn.call === 'function') { + return fn.apply(thisArg, args); + } + }, + + /** + * Note(SB) for performance sake, this method should only be used when loopable type + * is unknown or in none intensive code (not called often and small loopable). Else + * it's preferable to use a regular for() loop and save extra function calls. + * @param {object|Array} loopable - The object or array to be iterated. + * @param {function} fn - The function to call for each item. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @param {boolean} [reverse] - If true, iterates backward on the loopable. + */ + each: function(loopable, fn, thisArg, reverse) { + var i, len, keys; + if (helpers.isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + fn.call(thisArg, loopable[i], i); + } + } else { + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[i], i); + } + } + } else if (helpers.isObject(loopable)) { + keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[keys[i]], keys[i]); + } + } + }, + + /** + * Returns true if the `a0` and `a1` arrays have the same content, else returns false. + * @see https://stackoverflow.com/a/14853974 + * @param {Array} a0 - The array to compare + * @param {Array} a1 - The array to compare + * @returns {boolean} + */ + arrayEquals: function(a0, a1) { + var i, ilen, v0, v1; + + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen = a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0 instanceof Array && v1 instanceof Array) { + if (!helpers.arrayEquals(v0, v1)) { + return false; + } + } else if (v0 !== v1) { + // NOTE: two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + + return true; + }, + + /** + * Returns a deep copy of `source` without keeping references on objects and arrays. + * @param {*} source - The value to clone. + * @returns {*} + */ + clone: function(source) { + if (helpers.isArray(source)) { + return source.map(helpers.clone); + } + + if (helpers.isObject(source)) { + var target = {}; + var keys = Object.keys(source); + var klen = keys.length; + var k = 0; + + for (; k < klen; ++k) { + target[keys[k]] = helpers.clone(source[keys[k]]); + } + + return target; + } + + return source; + }, + + /** + * The default merger when Chart.helpers.merge is called without merger option. + * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback. + * @private + */ + _merger: function(key, target, source, options) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.merge(tval, sval, options); + } else { + target[key] = helpers.clone(sval); + } + }, + + /** + * Merges source[key] in target[key] only if target[key] is undefined. + * @private + */ + _mergerIf: function(key, target, source) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.mergeIf(tval, sval); + } else if (!target.hasOwnProperty(key)) { + target[key] = helpers.clone(sval); + } + }, + + /** + * Recursively deep copies `source` properties into `target` with the given `options`. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @param {object} [options] - Merging options: + * @param {function} [options.merger] - The merge method (key, target, source, options) + * @returns {object} The `target` object. + */ + merge: function(target, source, options) { + var sources = helpers.isArray(source) ? source : [source]; + var ilen = sources.length; + var merge, i, keys, klen, k; + + if (!helpers.isObject(target)) { + return target; + } + + options = options || {}; + merge = options.merger || helpers._merger; + + for (i = 0; i < ilen; ++i) { + source = sources[i]; + if (!helpers.isObject(source)) { + continue; + } + + keys = Object.keys(source); + for (k = 0, klen = keys.length; k < klen; ++k) { + merge(keys[k], target, source, options); + } + } + + return target; + }, + + /** + * Recursively deep copies `source` properties into `target` *only* if not defined in target. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @returns {object} The `target` object. + */ + mergeIf: function(target, source) { + return helpers.merge(target, source, {merger: helpers._mergerIf}); + }, + + /** + * Applies the contents of two or more objects together into the first object. + * @param {object} target - The target object in which all objects are merged into. + * @param {object} arg1 - Object containing additional properties to merge in target. + * @param {object} argN - Additional objects containing properties to merge in target. + * @returns {object} The `target` object. + */ + extend: Object.assign || function(target) { + return helpers.merge(target, [].slice.call(arguments, 1), { + merger: function(key, dst, src) { + dst[key] = src[key]; + } + }); + }, + + /** + * Basic javascript inheritance based on the model created in Backbone.js + */ + inherits: function(extensions) { + var me = this; + var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { + return me.apply(this, arguments); + }; + + var Surrogate = function() { + this.constructor = ChartElement; + }; + + Surrogate.prototype = me.prototype; + ChartElement.prototype = new Surrogate(); + ChartElement.extend = helpers.inherits; + + if (extensions) { + helpers.extend(ChartElement.prototype, extensions); + } + + ChartElement.__super__ = me.prototype; + return ChartElement; + }, + + _deprecated: function(scope, value, previous, current) { + if (value !== undefined) { + console.warn(scope + ': "' + previous + + '" is deprecated. Please use "' + current + '" instead'); + } + } +}; + +var helpers_core = helpers; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.callback instead. + * @function Chart.helpers.callCallback + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +helpers.callCallback = helpers.callback; + +/** + * Provided for backward compatibility, use Array.prototype.indexOf instead. + * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ + * @function Chart.helpers.indexOf + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.indexOf = function(array, item, fromIndex) { + return Array.prototype.indexOf.call(array, item, fromIndex); +}; + +/** + * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. + * @function Chart.helpers.getValueOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueOrDefault = helpers.valueOrDefault; + +/** + * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. + * @function Chart.helpers.getValueAtIndexOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + +/** + * Easing functions adapted from Robert Penner's easing equations. + * @namespace Chart.helpers.easingEffects + * @see http://www.robertpenner.com/easing/ + */ +var effects = { + linear: function(t) { + return t; + }, + + easeInQuad: function(t) { + return t * t; + }, + + easeOutQuad: function(t) { + return -t * (t - 2); + }, + + easeInOutQuad: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t; + } + return -0.5 * ((--t) * (t - 2) - 1); + }, + + easeInCubic: function(t) { + return t * t * t; + }, + + easeOutCubic: function(t) { + return (t = t - 1) * t * t + 1; + }, + + easeInOutCubic: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t; + } + return 0.5 * ((t -= 2) * t * t + 2); + }, + + easeInQuart: function(t) { + return t * t * t * t; + }, + + easeOutQuart: function(t) { + return -((t = t - 1) * t * t * t - 1); + }, + + easeInOutQuart: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t; + } + return -0.5 * ((t -= 2) * t * t * t - 2); + }, + + easeInQuint: function(t) { + return t * t * t * t * t; + }, + + easeOutQuint: function(t) { + return (t = t - 1) * t * t * t * t + 1; + }, + + easeInOutQuint: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t * t; + } + return 0.5 * ((t -= 2) * t * t * t * t + 2); + }, + + easeInSine: function(t) { + return -Math.cos(t * (Math.PI / 2)) + 1; + }, + + easeOutSine: function(t) { + return Math.sin(t * (Math.PI / 2)); + }, + + easeInOutSine: function(t) { + return -0.5 * (Math.cos(Math.PI * t) - 1); + }, + + easeInExpo: function(t) { + return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); + }, + + easeOutExpo: function(t) { + return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; + }, + + easeInOutExpo: function(t) { + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if ((t /= 0.5) < 1) { + return 0.5 * Math.pow(2, 10 * (t - 1)); + } + return 0.5 * (-Math.pow(2, -10 * --t) + 2); + }, + + easeInCirc: function(t) { + if (t >= 1) { + return t; + } + return -(Math.sqrt(1 - t * t) - 1); + }, + + easeOutCirc: function(t) { + return Math.sqrt(1 - (t = t - 1) * t); + }, + + easeInOutCirc: function(t) { + if ((t /= 0.5) < 1) { + return -0.5 * (Math.sqrt(1 - t * t) - 1); + } + return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + + easeInElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + }, + + easeOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; + }, + + easeInOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if ((t /= 0.5) === 2) { + return 1; + } + if (!p) { + p = 0.45; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + if (t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + easeInBack: function(t) { + var s = 1.70158; + return t * t * ((s + 1) * t - s); + }, + + easeOutBack: function(t) { + var s = 1.70158; + return (t = t - 1) * t * ((s + 1) * t + s) + 1; + }, + + easeInOutBack: function(t) { + var s = 1.70158; + if ((t /= 0.5) < 1) { + return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); + } + return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + + easeInBounce: function(t) { + return 1 - effects.easeOutBounce(1 - t); + }, + + easeOutBounce: function(t) { + if (t < (1 / 2.75)) { + return 7.5625 * t * t; + } + if (t < (2 / 2.75)) { + return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; + } + if (t < (2.5 / 2.75)) { + return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; + } + return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; + }, + + easeInOutBounce: function(t) { + if (t < 0.5) { + return effects.easeInBounce(t * 2) * 0.5; + } + return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; + } +}; + +var helpers_easing = { + effects: effects +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.easing.effects instead. + * @function Chart.helpers.easingEffects + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.easingEffects = effects; + +var PI = Math.PI; +var RAD_PER_DEG = PI / 180; +var DOUBLE_PI = PI * 2; +var HALF_PI = PI / 2; +var QUARTER_PI = PI / 4; +var TWO_THIRDS_PI = PI * 2 / 3; + +/** + * @namespace Chart.helpers.canvas + */ +var exports$1 = { + /** + * Clears the entire canvas associated to the given `chart`. + * @param {Chart} chart - The chart for which to clear the canvas. + */ + clear: function(chart) { + chart.ctx.clearRect(0, 0, chart.width, chart.height); + }, + + /** + * Creates a "path" for a rectangle with rounded corners at position (x, y) with a + * given size (width, height) and the same `radius` for all corners. + * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. + * @param {number} x - The x axis of the coordinate for the rectangle starting point. + * @param {number} y - The y axis of the coordinate for the rectangle starting point. + * @param {number} width - The rectangle's width. + * @param {number} height - The rectangle's height. + * @param {number} radius - The rounded amount (in pixels) for the four corners. + * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? + */ + roundedRect: function(ctx, x, y, width, height, radius) { + if (radius) { + var r = Math.min(radius, height / 2, width / 2); + var left = x + r; + var top = y + r; + var right = x + width - r; + var bottom = y + height - r; + + ctx.moveTo(x, top); + if (left < right && top < bottom) { + ctx.arc(left, top, r, -PI, -HALF_PI); + ctx.arc(right, top, r, -HALF_PI, 0); + ctx.arc(right, bottom, r, 0, HALF_PI); + ctx.arc(left, bottom, r, HALF_PI, PI); + } else if (left < right) { + ctx.moveTo(left, y); + ctx.arc(right, top, r, -HALF_PI, HALF_PI); + ctx.arc(left, top, r, HALF_PI, PI + HALF_PI); + } else if (top < bottom) { + ctx.arc(left, top, r, -PI, 0); + ctx.arc(left, bottom, r, 0, PI); + } else { + ctx.arc(left, top, r, -PI, PI); + } + ctx.closePath(); + ctx.moveTo(x, y); + } else { + ctx.rect(x, y, width, height); + } + }, + + drawPoint: function(ctx, style, radius, x, y, rotation) { + var type, xOffset, yOffset, size, cornerRadius; + var rad = (rotation || 0) * RAD_PER_DEG; + + if (style && typeof style === 'object') { + type = style.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rad); + ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); + ctx.restore(); + return; + } + } + + if (isNaN(radius) || radius <= 0) { + return; + } + + ctx.beginPath(); + + switch (style) { + // Default includes circle + default: + ctx.arc(x, y, radius, 0, DOUBLE_PI); + ctx.closePath(); + break; + case 'triangle': + ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + ctx.closePath(); + break; + case 'rectRounded': + // NOTE: the rounded rect implementation changed to use `arc` instead of + // `quadraticCurveTo` since it generates better results when rect is + // almost a circle. 0.516 (instead of 0.5) produces results with visually + // closer proportion to the previous impl and it is inscribed in the + // circle with `radius`. For more details, see the following PRs: + // https://github.com/chartjs/Chart.js/issues/5597 + // https://github.com/chartjs/Chart.js/issues/5858 + cornerRadius = radius * 0.516; + size = radius - cornerRadius; + xOffset = Math.cos(rad + QUARTER_PI) * size; + yOffset = Math.sin(rad + QUARTER_PI) * size; + ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); + ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); + ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); + ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); + ctx.closePath(); + break; + case 'rect': + if (!rotation) { + size = Math.SQRT1_2 * radius; + ctx.rect(x - size, y - size, 2 * size, 2 * size); + break; + } + rad += QUARTER_PI; + /* falls through */ + case 'rectRot': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + yOffset, y - xOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.lineTo(x - yOffset, y + xOffset); + ctx.closePath(); + break; + case 'crossRot': + rad += QUARTER_PI; + /* falls through */ + case 'cross': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'star': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + rad += QUARTER_PI; + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'line': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + break; + case 'dash': + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); + break; + } + + ctx.fill(); + ctx.stroke(); + }, + + /** + * Returns true if the point is inside the rectangle + * @param {object} point - The point to test + * @param {object} area - The rectangle + * @returns {boolean} + * @private + */ + _isPointInArea: function(point, area) { + var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. + + return point.x > area.left - epsilon && point.x < area.right + epsilon && + point.y > area.top - epsilon && point.y < area.bottom + epsilon; + }, + + clipArea: function(ctx, area) { + ctx.save(); + ctx.beginPath(); + ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); + ctx.clip(); + }, + + unclipArea: function(ctx) { + ctx.restore(); + }, + + lineTo: function(ctx, previous, target, flip) { + var stepped = target.steppedLine; + if (stepped) { + if (stepped === 'middle') { + var midpoint = (previous.x + target.x) / 2.0; + ctx.lineTo(midpoint, flip ? target.y : previous.y); + ctx.lineTo(midpoint, flip ? previous.y : target.y); + } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) { + ctx.lineTo(previous.x, target.y); + } else { + ctx.lineTo(target.x, previous.y); + } + ctx.lineTo(target.x, target.y); + return; + } + + if (!target.tension) { + ctx.lineTo(target.x, target.y); + return; + } + + ctx.bezierCurveTo( + flip ? previous.controlPointPreviousX : previous.controlPointNextX, + flip ? previous.controlPointPreviousY : previous.controlPointNextY, + flip ? target.controlPointNextX : target.controlPointPreviousX, + flip ? target.controlPointNextY : target.controlPointPreviousY, + target.x, + target.y); + } +}; + +var helpers_canvas = exports$1; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. + * @namespace Chart.helpers.clear + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.clear = exports$1.clear; + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. + * @namespace Chart.helpers.drawRoundedRectangle + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.drawRoundedRectangle = function(ctx) { + ctx.beginPath(); + exports$1.roundedRect.apply(exports$1, arguments); +}; + +var defaults = { + /** + * @private + */ + _set: function(scope, values) { + return helpers_core.merge(this[scope] || (this[scope] = {}), values); + } +}; + +// TODO(v3): remove 'global' from namespace. all default are global and +// there's inconsistency around which options are under 'global' +defaults._set('global', { + defaultColor: 'rgba(0,0,0,0.1)', + defaultFontColor: '#666', + defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + defaultFontSize: 12, + defaultFontStyle: 'normal', + defaultLineHeight: 1.2, + showLines: true +}); + +var core_defaults = defaults; + +var valueOrDefault = helpers_core.valueOrDefault; + +/** + * Converts the given font object into a CSS font string. + * @param {object} font - A font object. + * @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font + * @private + */ +function toFontString(font) { + if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) { + return null; + } + + return (font.style ? font.style + ' ' : '') + + (font.weight ? font.weight + ' ' : '') + + font.size + 'px ' + + font.family; +} + +/** + * @alias Chart.helpers.options + * @namespace + */ +var helpers_options = { + /** + * Converts the given line height `value` in pixels for a specific font `size`. + * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). + * @param {number} size - The font size (in pixels) used to resolve relative `value`. + * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid). + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height + * @since 2.7.0 + */ + toLineHeight: function(value, size) { + var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); + if (!matches || matches[1] === 'normal') { + return size * 1.2; + } + + value = +matches[2]; + + switch (matches[3]) { + case 'px': + return value; + case '%': + value /= 100; + break; + } + + return size * value; + }, + + /** + * Converts the given value into a padding object with pre-computed width/height. + * @param {number|object} value - If a number, set the value to all TRBL component, + * else, if and object, use defined properties and sets undefined ones to 0. + * @returns {object} The padding values (top, right, bottom, left, width, height) + * @since 2.7.0 + */ + toPadding: function(value) { + var t, r, b, l; + + if (helpers_core.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + top: t, + right: r, + bottom: b, + left: l, + height: t + b, + width: l + r + }; + }, + + /** + * Parses font options and returns the font object. + * @param {object} options - A object that contains font options to be parsed. + * @return {object} The font object. + * @todo Support font.* options and renamed to toFont(). + * @private + */ + _parseFont: function(options) { + var globalDefaults = core_defaults.global; + var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var font = { + family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), + lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), + size: size, + style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), + weight: null, + string: '' + }; + + font.string = toFontString(font); + return font; + }, + + /** + * Evaluates the given `inputs` sequentially and returns the first defined value. + * @param {Array} inputs - An array of values, falling back to the last value. + * @param {object} [context] - If defined and the current value is a function, the value + * is called with `context` as first argument and the result becomes the new input. + * @param {number} [index] - If defined and the current value is an array, the value + * at `index` become the new input. + * @param {object} [info] - object to return information about resolution in + * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable. + * @since 2.7.0 + */ + resolve: function(inputs, context, index, info) { + var cacheable = true; + var i, ilen, value; + + for (i = 0, ilen = inputs.length; i < ilen; ++i) { + value = inputs[i]; + if (value === undefined) { + continue; + } + if (context !== undefined && typeof value === 'function') { + value = value(context); + cacheable = false; + } + if (index !== undefined && helpers_core.isArray(value)) { + value = value[index]; + cacheable = false; + } + if (value !== undefined) { + if (info && !cacheable) { + info.cacheable = false; + } + return value; + } + } + } +}; + +/** + * @alias Chart.helpers.math + * @namespace + */ +var exports$2 = { + /** + * Returns an array of factors sorted from 1 to sqrt(value) + * @private + */ + _factorize: function(value) { + var result = []; + var sqrt = Math.sqrt(value); + var i; + + for (i = 1; i < sqrt; i++) { + if (value % i === 0) { + result.push(i); + result.push(value / i); + } + } + if (sqrt === (sqrt | 0)) { // if value is a square number + result.push(sqrt); + } + + result.sort(function(a, b) { + return a - b; + }).pop(); + return result; + }, + + log10: Math.log10 || function(x) { + var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. + // Check for whole powers of 10, + // which due to floating point rounding error should be corrected. + var powerOf10 = Math.round(exponent); + var isPowerOf10 = x === Math.pow(10, powerOf10); + + return isPowerOf10 ? powerOf10 : exponent; + } +}; + +var helpers_math = exports$2; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.math.log10 instead. + * @namespace Chart.helpers.log10 + * @deprecated since version 2.9.0 + * @todo remove at version 3 + * @private + */ +helpers_core.log10 = exports$2.log10; + +var getRtlAdapter = function(rectX, width) { + return { + x: function(x) { + return rectX + rectX + width - x; + }, + setWidth: function(w) { + width = w; + }, + textAlign: function(align) { + if (align === 'center') { + return align; + } + return align === 'right' ? 'left' : 'right'; + }, + xPlus: function(x, value) { + return x - value; + }, + leftForLtr: function(x, itemWidth) { + return x - itemWidth; + }, + }; +}; + +var getLtrAdapter = function() { + return { + x: function(x) { + return x; + }, + setWidth: function(w) { // eslint-disable-line no-unused-vars + }, + textAlign: function(align) { + return align; + }, + xPlus: function(x, value) { + return x + value; + }, + leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars + return x; + }, + }; +}; + +var getAdapter = function(rtl, rectX, width) { + return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter(); +}; + +var overrideTextDirection = function(ctx, direction) { + var style, original; + if (direction === 'ltr' || direction === 'rtl') { + style = ctx.canvas.style; + original = [ + style.getPropertyValue('direction'), + style.getPropertyPriority('direction'), + ]; + + style.setProperty('direction', direction, 'important'); + ctx.prevTextDirection = original; + } +}; + +var restoreTextDirection = function(ctx) { + var original = ctx.prevTextDirection; + if (original !== undefined) { + delete ctx.prevTextDirection; + ctx.canvas.style.setProperty('direction', original[0], original[1]); + } +}; + +var helpers_rtl = { + getRtlAdapter: getAdapter, + overrideTextDirection: overrideTextDirection, + restoreTextDirection: restoreTextDirection, +}; + +var helpers$1 = helpers_core; +var easing = helpers_easing; +var canvas = helpers_canvas; +var options = helpers_options; +var math = helpers_math; +var rtl = helpers_rtl; +helpers$1.easing = easing; +helpers$1.canvas = canvas; +helpers$1.options = options; +helpers$1.math = math; +helpers$1.rtl = rtl; + +function interpolate(start, view, model, ease) { + var keys = Object.keys(model); + var i, ilen, key, actual, origin, target, type, c0, c1; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + + target = model[key]; + + // if a value is added to the model after pivot() has been called, the view + // doesn't contain it, so let's initialize the view to the target value. + if (!view.hasOwnProperty(key)) { + view[key] = target; + } + + actual = view[key]; + + if (actual === target || key[0] === '_') { + continue; + } + + if (!start.hasOwnProperty(key)) { + start[key] = actual; + } + + origin = start[key]; + + type = typeof target; + + if (type === typeof origin) { + if (type === 'string') { + c0 = chartjsColor(origin); + if (c0.valid) { + c1 = chartjsColor(target); + if (c1.valid) { + view[key] = c1.mix(c0, ease).rgbString(); + continue; + } + } + } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) { + view[key] = origin + (target - origin) * ease; + continue; + } + } + + view[key] = target; + } +} + +var Element = function(configuration) { + helpers$1.extend(this, configuration); + this.initialize.apply(this, arguments); +}; + +helpers$1.extend(Element.prototype, { + _type: undefined, + + initialize: function() { + this.hidden = false; + }, + + pivot: function() { + var me = this; + if (!me._view) { + me._view = helpers$1.extend({}, me._model); + } + me._start = {}; + return me; + }, + + transition: function(ease) { + var me = this; + var model = me._model; + var start = me._start; + var view = me._view; + + // No animation -> No Transition + if (!model || ease === 1) { + me._view = helpers$1.extend({}, model); + me._start = null; + return me; + } + + if (!view) { + view = me._view = {}; + } + + if (!start) { + start = me._start = {}; + } + + interpolate(start, view, model, ease); + + return me; + }, + + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + + hasValue: function() { + return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y); + } +}); + +Element.extend = helpers$1.inherits; + +var core_element = Element; + +var exports$3 = core_element.extend({ + chart: null, // the animation associated chart instance + currentStep: 0, // the current animation step + numSteps: 60, // default number of steps + easing: '', // the easing to use for this animation + render: null, // render function used by the animation service + + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null, // user specified callback to fire when the animation finishes +}); + +var core_animation = exports$3; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.Animation instead + * @prop Chart.Animation#animationObject + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports$3.prototype, 'animationObject', { + get: function() { + return this; + } +}); + +/** + * Provided for backward compatibility, use Chart.Animation#chart instead + * @prop Chart.Animation#chartInstance + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports$3.prototype, 'chartInstance', { + get: function() { + return this.chart; + }, + set: function(value) { + this.chart = value; + } +}); + +core_defaults._set('global', { + animation: { + duration: 1000, + easing: 'easeOutQuart', + onProgress: helpers$1.noop, + onComplete: helpers$1.noop + } +}); + +var core_animations = { + animations: [], + request: null, + + /** + * @param {Chart} chart - The chart to animate. + * @param {Chart.Animation} animation - The animation that we will animate. + * @param {number} duration - The animation duration in ms. + * @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions + */ + addAnimation: function(chart, animation, duration, lazy) { + var animations = this.animations; + var i, ilen; + + animation.chart = chart; + animation.startTime = Date.now(); + animation.duration = duration; + + if (!lazy) { + chart.animating = true; + } + + for (i = 0, ilen = animations.length; i < ilen; ++i) { + if (animations[i].chart === chart) { + animations[i] = animation; + return; + } + } + + animations.push(animation); + + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (animations.length === 1) { + this.requestAnimationFrame(); + } + }, + + cancelAnimation: function(chart) { + var index = helpers$1.findIndex(this.animations, function(animation) { + return animation.chart === chart; + }); + + if (index !== -1) { + this.animations.splice(index, 1); + chart.animating = false; + } + }, + + requestAnimationFrame: function() { + var me = this; + if (me.request === null) { + return; + // TBD: animation should work somehow; what is startDigest? + + // Skip animation frame requests until the active one is executed. + // This can happen when processing mouse events, e.g. 'mousemove' + // and 'mouseout' events will trigger multiple renders. + me.request = helpers$1.requestAnimFrame.call(undefined, function() { + me.request = null; + me.startDigest(); + }); + } + }, + + /** + * @private + */ + startDigest: function() { + var me = this; + + me.advance(); + + // Do we have more stuff to animate? + if (me.animations.length > 0) { + me.requestAnimationFrame(); + } + }, + + /** + * @private + */ + advance: function() { + var animations = this.animations; + var animation, chart, numSteps, nextStep; + var i = 0; + + // 1 animation per chart, so we are looping charts here + while (i < animations.length) { + animation = animations[i]; + chart = animation.chart; + numSteps = animation.numSteps; + + // Make sure that currentStep starts at 1 + // https://github.com/chartjs/Chart.js/issues/6104 + nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1; + animation.currentStep = Math.min(nextStep, numSteps); + + helpers$1.callback(animation.render, [chart, animation], chart); + helpers$1.callback(animation.onAnimationProgress, [animation], chart); + + if (animation.currentStep >= numSteps) { + helpers$1.callback(animation.onAnimationComplete, [animation], chart); + chart.animating = false; + animations.splice(i, 1); + } else { + ++i; + } + } + } +}; + +var resolve = helpers$1.options.resolve; + +var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; + +/** + * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', + * 'unshift') and notify the listener AFTER the array has been altered. Listeners are + * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. + */ +function listenArrayEvents(array, listener) { + if (array._chartjs) { + array._chartjs.listeners.push(listener); + return; + } + + Object.defineProperty(array, '_chartjs', { + configurable: true, + enumerable: false, + value: { + listeners: [listener] + } + }); + + arrayEvents.forEach(function(key) { + var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); + var base = array[key]; + + Object.defineProperty(array, key, { + configurable: true, + enumerable: false, + value: function() { + var args = Array.prototype.slice.call(arguments); + var res = base.apply(this, args); + + helpers$1.each(array._chartjs.listeners, function(object) { + if (typeof object[method] === 'function') { + object[method].apply(object, args); + } + }); + + return res; + } + }); + }); +} + +/** + * Removes the given array event listener and cleanup extra attached properties (such as + * the _chartjs stub and overridden methods) if array doesn't have any more listeners. + */ +function unlistenArrayEvents(array, listener) { + var stub = array._chartjs; + if (!stub) { + return; + } + + var listeners = stub.listeners; + var index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + + if (listeners.length > 0) { + return; + } + + arrayEvents.forEach(function(key) { + delete array[key]; + }); + + delete array._chartjs; +} + +// Base class for all dataset controllers (line, bar, etc) +var DatasetController = function(chart, datasetIndex) { + this.initialize(chart, datasetIndex); +}; + +helpers$1.extend(DatasetController.prototype, { + + /** + * Element type used to generate a meta dataset (e.g. Chart.element.Line). + * @type {Chart.core.element} + */ + datasetElementType: null, + + /** + * Element type used to generate a meta data (e.g. Chart.element.Point). + * @type {Chart.core.element} + */ + dataElementType: null, + + /** + * Dataset element option keys to be resolved in _resolveDatasetElementOptions. + * A derived controller may override this to resolve controller-specific options. + * The keys defined here are for backward compatibility for legend styles. + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth' + ], + + /** + * Data element option keys to be resolved in _resolveDataElementOptions. + * A derived controller may override this to resolve controller-specific options. + * The keys defined here are for backward compatibility for legend styles. + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'pointStyle' + ], + + initialize: function(chart, datasetIndex) { + var me = this; + me.chart = chart; + me.index = datasetIndex; + me.linkScales(); + me.addElements(); + me._type = me.getMeta().type; + }, + + updateIndex: function(datasetIndex) { + this.index = datasetIndex; + }, + + linkScales: function() { + var me = this; + var meta = me.getMeta(); + var chart = me.chart; + var scales = chart.scales; + var dataset = me.getDataset(); + var scalesOpts = chart.options.scales; + + if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) { + meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id; + } + if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) { + meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id; + } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, + + getMeta: function() { + return this.chart.getDatasetMeta(this.index); + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.getMeta().yAxisID; + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + _getValueScale: function() { + return this.getScaleForId(this._getValueScaleId()); + }, + + /** + * @private + */ + _getIndexScale: function() { + return this.getScaleForId(this._getIndexScaleId()); + }, + + reset: function() { + this._update(true); + }, + + /** + * @private + */ + destroy: function() { + if (this._data) { + unlistenArrayEvents(this._data, this); + } + }, + + createMetaDataset: function() { + var me = this; + var type = me.datasetElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index + }); + }, + + createMetaData: function(index) { + var me = this; + var type = me.dataElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index, + _index: index + }); + }, + + addElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data || []; + var metaData = meta.data; + var i, ilen; + + for (i = 0, ilen = data.length; i < ilen; ++i) { + metaData[i] = metaData[i] || me.createMetaData(i); + } + + meta.dataset = meta.dataset || me.createMetaDataset(); + }, + + addElementAndReset: function(index) { + var element = this.createMetaData(index); + this.getMeta().data.splice(index, 0, element); + this.updateElement(element, index, true); + }, + + buildOrUpdateElements: function() { + var me = this; + var dataset = me.getDataset(); + var data = dataset.data || (dataset.data = []); + + // In order to correctly handle data addition/deletion animation (an thus simulate + // real-time charts), we need to monitor these data modifications and synchronize + // the internal meta data accordingly. + if (me._data !== data) { + if (me._data) { + // This case happens when the user replaced the data array instance. + unlistenArrayEvents(me._data, me); + } + + if (data && Object.isExtensible(data)) { + listenArrayEvents(data, me); + } + me._data = data; + } + + // Re-sync meta data in case the user replaced the data array or if we missed + // any updates and so make sure that we handle number of datapoints changing. + me.resyncElements(); + }, + + /** + * Returns the merged user-supplied and default dataset-level options + * @private + */ + _configure: function() { + var me = this; + me._config = helpers$1.merge({}, [ + me.chart.options.datasets[me._type], + me.getDataset(), + ], { + merger: function(key, target, source) { + if (key !== '_meta' && key !== 'data') { + helpers$1._merger(key, target, source); + } + } + }); + }, + + _update: function(reset) { + var me = this; + me._configure(); + me._cachedDataOpts = null; + me.update(reset); + }, + + update: helpers$1.noop, + + transition: function(easingValue) { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + for (; i < ilen; ++i) { + elements[i].transition(easingValue); + } + + if (meta.dataset) { + meta.dataset.transition(easingValue); + } + }, + + draw: function() { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + if (meta.dataset) { + meta.dataset.draw(); + } + + for (; i < ilen; ++i) { + elements[i].draw(); + } + }, + + /** + * Returns a set of predefined style properties that should be used to represent the dataset + * or the data if the index is specified + * @param {number} index - data index + * @return {IStyleInterface} style object + */ + getStyle: function(index) { + var me = this; + var meta = me.getMeta(); + var dataset = meta.dataset; + var style; + + me._configure(); + if (dataset && index === undefined) { + style = me._resolveDatasetElementOptions(dataset || {}); + } else { + index = index || 0; + style = me._resolveDataElementOptions(meta.data[index] || {}, index); + } + + if (style.fill === false || style.fill === null) { + style.backgroundColor = style.borderColor; + } + + return style; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function(element, hover) { + var me = this; + var chart = me.chart; + var datasetOpts = me._config; + var custom = element.custom || {}; + var options = chart.options.elements[me.datasetElementType.prototype._type] || {}; + var elementOptions = me._datasetElementOptions; + var values = {}; + var i, ilen, key, readKey; + + // Scriptable options + var context = { + chart: chart, + dataset: me.getDataset(), + datasetIndex: me.index, + hover: hover + }; + + for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { + key = elementOptions[i]; + readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key; + values[key] = resolve([ + custom[readKey], + datasetOpts[readKey], + options[readKey] + ], context); + } + + return values; + }, + + /** + * @private + */ + _resolveDataElementOptions: function(element, index) { + var me = this; + var custom = element && element.custom; + var cached = me._cachedDataOpts; + if (cached && !custom) { + return cached; + } + var chart = me.chart; + var datasetOpts = me._config; + var options = chart.options.elements[me.dataElementType.prototype._type] || {}; + var elementOptions = me._dataElementOptions; + var values = {}; + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: me.getDataset(), + datasetIndex: me.index + }; + + // `resolve` sets cacheable to `false` if any option is indexed or scripted + var info = {cacheable: !custom}; + + var keys, i, ilen, key; + + custom = custom || {}; + + if (helpers$1.isArray(elementOptions)) { + for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { + key = elementOptions[i]; + values[key] = resolve([ + custom[key], + datasetOpts[key], + options[key] + ], context, index, info); + } + } else { + keys = Object.keys(elementOptions); + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + values[key] = resolve([ + custom[key], + datasetOpts[elementOptions[key]], + datasetOpts[key], + options[key] + ], context, index, info); + } + } + + if (info.cacheable) { + me._cachedDataOpts = Object.freeze(values); + } + + return values; + }, + + removeHoverStyle: function(element) { + helpers$1.merge(element._model, element.$previousStyle || {}); + delete element.$previousStyle; + }, + + setHoverStyle: function(element) { + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var model = element._model; + var getHoverColor = helpers$1.getHoverColor; + + element.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth + }; + + model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index); + model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index); + model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index); + }, + + /** + * @private + */ + _removeDatasetHoverStyle: function() { + var element = this.getMeta().dataset; + + if (element) { + this.removeHoverStyle(element); + } + }, + + /** + * @private + */ + _setDatasetHoverStyle: function() { + var element = this.getMeta().dataset; + var prev = {}; + var i, ilen, key, keys, hoverOptions, model; + + if (!element) { + return; + } + + model = element._model; + hoverOptions = this._resolveDatasetElementOptions(element, true); + + keys = Object.keys(hoverOptions); + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + prev[key] = model[key]; + model[key] = hoverOptions[key]; + } + + element.$previousStyle = prev; + }, + + /** + * @private + */ + resyncElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data; + var numMeta = meta.data.length; + var numData = data.length; + + if (numData < numMeta) { + meta.data.splice(numData, numMeta - numData); + } else if (numData > numMeta) { + me.insertElements(numMeta, numData - numMeta); + } + }, + + /** + * @private + */ + insertElements: function(start, count) { + for (var i = 0; i < count; ++i) { + this.addElementAndReset(start + i); + } + }, + + /** + * @private + */ + onDataPush: function() { + var count = arguments.length; + this.insertElements(this.getDataset().data.length - count, count); + }, + + /** + * @private + */ + onDataPop: function() { + this.getMeta().data.pop(); + }, + + /** + * @private + */ + onDataShift: function() { + this.getMeta().data.shift(); + }, + + /** + * @private + */ + onDataSplice: function(start, count) { + this.getMeta().data.splice(start, count); + this.insertElements(start, arguments.length - 2); + }, + + /** + * @private + */ + onDataUnshift: function() { + this.insertElements(0, arguments.length); + } +}); + +DatasetController.extend = helpers$1.inherits; + +var core_datasetController = DatasetController; + +var TAU = Math.PI * 2; + +core_defaults._set('global', { + elements: { + arc: { + backgroundColor: core_defaults.global.defaultColor, + borderColor: '#fff', + borderWidth: 2, + borderAlign: 'center' + } + } +}); + +function clipArc(ctx, arc) { + var startAngle = arc.startAngle; + var endAngle = arc.endAngle; + var pixelMargin = arc.pixelMargin; + var angleMargin = pixelMargin / arc.outerRadius; + var x = arc.x; + var y = arc.y; + + // Draw an inner border by cliping the arc and drawing a double-width border + // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders + ctx.beginPath(); + ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin); + if (arc.innerRadius > pixelMargin) { + angleMargin = pixelMargin / arc.innerRadius; + ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true); + } else { + ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2); + } + ctx.closePath(); + ctx.clip(); +} + +function drawFullCircleBorders(ctx, vm, arc, inner) { + var endAngle = arc.endAngle; + var i; + + if (inner) { + arc.endAngle = arc.startAngle + TAU; + clipArc(ctx, arc); + arc.endAngle = endAngle; + if (arc.endAngle === arc.startAngle && arc.fullCircles) { + arc.endAngle += TAU; + arc.fullCircles--; + } + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.stroke(); + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.stroke(); + } +} + +function drawBorder(ctx, vm, arc) { + var inner = vm.borderAlign === 'inner'; + + if (inner) { + ctx.lineWidth = vm.borderWidth * 2; + ctx.lineJoin = 'round'; + } else { + ctx.lineWidth = vm.borderWidth; + ctx.lineJoin = 'bevel'; + } + + if (arc.fullCircles) { + drawFullCircleBorders(ctx, vm, arc, inner); + } + + if (inner) { + clipArc(ctx, arc); + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + ctx.stroke(); +} + +var element_arc = core_element.extend({ + _type: 'arc', + + inLabelRange: function(mouseX) { + var vm = this._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } + return false; + }, + + inRange: function(chartX, chartY) { + var vm = this._view; + + if (vm) { + var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY}); + var angle = pointRelativePosition.angle; + var distance = pointRelativePosition.distance; + + // Sanitise angle range + var startAngle = vm.startAngle; + var endAngle = vm.endAngle; + while (endAngle < startAngle) { + endAngle += TAU; + } + while (angle > endAngle) { + angle -= TAU; + } + while (angle < startAngle) { + angle += TAU; + } + + // Check if within the range of the open/close angle + var betweenAngles = (angle >= startAngle && angle <= endAngle); + var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); + + return (betweenAngles && withinRadius); + } + return false; + }, + + getCenterPoint: function() { + var vm = this._view; + var halfAngle = (vm.startAngle + vm.endAngle) / 2; + var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; + return { + x: vm.x + Math.cos(halfAngle) * halfRadius, + y: vm.y + Math.sin(halfAngle) * halfRadius + }; + }, + + getArea: function() { + var vm = this._view; + return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); + }, + + tooltipPosition: function() { + var vm = this._view; + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); + var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0; + var arc = { + x: vm.x, + y: vm.y, + innerRadius: vm.innerRadius, + outerRadius: Math.max(vm.outerRadius - pixelMargin, 0), + pixelMargin: pixelMargin, + startAngle: vm.startAngle, + endAngle: vm.endAngle, + fullCircles: Math.floor(vm.circumference / TAU) + }; + var i; + + ctx.save(); + + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + + if (arc.fullCircles) { + arc.endAngle = arc.startAngle + TAU; + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.fill(); + } + arc.endAngle = arc.startAngle + vm.circumference % TAU; + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + ctx.fill(); + + if (vm.borderWidth) { + drawBorder(ctx, vm, arc); + } + + ctx.restore(); + } +}); + +var valueOrDefault$1 = helpers$1.valueOrDefault; + +var defaultColor = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + line: { + tension: 0.4, + backgroundColor: defaultColor, + borderWidth: 3, + borderColor: defaultColor, + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0.0, + borderJoinStyle: 'miter', + capBezierPoints: true, + fill: true, // do we fill in the area between the line and its base axis + } + } +}); + +var element_line = core_element.extend({ + _type: 'line', + + draw: function() { + var me = this; + var vm = me._view; + var ctx = me._chart.ctx; + var spanGaps = vm.spanGaps; + var points = me._children.slice(); // clone array + var globalDefaults = core_defaults.global; + var globalOptionLineElements = globalDefaults.elements.line; + var lastDrawnIndex = -1; + var closePath = me._loop; + var index, previous, currentVM; + + if (!points.length) { + return; + } + + if (me._loop) { + for (index = 0; index < points.length; ++index) { + previous = helpers$1.previousItem(points, index); + // If the line has an open path, shift the point array + if (!points[index]._view.skip && previous._view.skip) { + points = points.slice(index).concat(points.slice(0, index)); + closePath = spanGaps; + break; + } + } + // If the line has a close path, add the first point again + if (closePath) { + points.push(points[0]); + } + } + + ctx.save(); + + // Stroke Line Options + ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; + + // IE 9 and 10 do not support line dash + if (ctx.setLineDash) { + ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); + } + + // line dash fix for QML + if(ctx.getLineDash && ctx.getLineDash().length === 0) { + ctx.setLineDash([99999]); + } + + ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset); + ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; + ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth); + ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; + + // Stroke Line + ctx.beginPath(); + + // First point moves to it's starting position no matter what + currentVM = points[0]._view; + if (!currentVM.skip) { + ctx.moveTo(currentVM.x, currentVM.y); + lastDrawnIndex = 0; + } + + for (index = 1; index < points.length; ++index) { + currentVM = points[index]._view; + previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex]; + + if (!currentVM.skip) { + if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { + // There was a gap and this is the first point after the gap + ctx.moveTo(currentVM.x, currentVM.y); + } else { + // Line to next point + helpers$1.canvas.lineTo(ctx, previous._view, currentVM); + } + lastDrawnIndex = index; + } + } + + if (closePath) { + ctx.closePath(); + } + + ctx.stroke(); + ctx.restore(); + } +}); + +var valueOrDefault$2 = helpers$1.valueOrDefault; + +var defaultColor$1 = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + point: { + radius: 3, + pointStyle: 'circle', + backgroundColor: defaultColor$1, + borderColor: defaultColor$1, + borderWidth: 1, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1 + } + } +}); + +function xRange(mouseX) { + var vm = this._view; + return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; +} + +function yRange(mouseY) { + var vm = this._view; + return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; +} + +var element_point = core_element.extend({ + _type: 'point', + + inRange: function(mouseX, mouseY) { + var vm = this._view; + return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; + }, + + inLabelRange: xRange, + inXRange: xRange, + inYRange: yRange, + + getCenterPoint: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + }, + + getArea: function() { + return Math.PI * Math.pow(this._view.radius, 2); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, + + draw: function(chartArea) { + var vm = this._view; + var ctx = this._chart.ctx; + var pointStyle = vm.pointStyle; + var rotation = vm.rotation; + var radius = vm.radius; + var x = vm.x; + var y = vm.y; + var globalDefaults = core_defaults.global; + var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow + + if (vm.skip) { + return; + } + + // Clipping for Points. + if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) { + ctx.strokeStyle = vm.borderColor || defaultColor; + ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth); + ctx.fillStyle = vm.backgroundColor || defaultColor; + helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); + } + } +}); + +var defaultColor$2 = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + rectangle: { + backgroundColor: defaultColor$2, + borderColor: defaultColor$2, + borderSkipped: 'bottom', + borderWidth: 0 + } + } +}); + +function isVertical(vm) { + return vm && vm.width !== undefined; +} + +/** + * Helper function to get the bounds of the bar regardless of the orientation + * @param bar {Chart.Element.Rectangle} the bar + * @return {Bounds} bounds of the bar + * @private + */ +function getBarBounds(vm) { + var x1, x2, y1, y2, half; + + if (isVertical(vm)) { + half = vm.width / 2; + x1 = vm.x - half; + x2 = vm.x + half; + y1 = Math.min(vm.y, vm.base); + y2 = Math.max(vm.y, vm.base); + } else { + half = vm.height / 2; + x1 = Math.min(vm.x, vm.base); + x2 = Math.max(vm.x, vm.base); + y1 = vm.y - half; + y2 = vm.y + half; + } + + return { + left: x1, + top: y1, + right: x2, + bottom: y2 + }; +} + +function swap(orig, v1, v2) { + return orig === v1 ? v2 : orig === v2 ? v1 : orig; +} + +function parseBorderSkipped(vm) { + var edge = vm.borderSkipped; + var res = {}; + + if (!edge) { + return res; + } + + if (vm.horizontal) { + if (vm.base > vm.x) { + edge = swap(edge, 'left', 'right'); + } + } else if (vm.base < vm.y) { + edge = swap(edge, 'bottom', 'top'); + } + + res[edge] = true; + return res; +} + +function parseBorderWidth(vm, maxW, maxH) { + var value = vm.borderWidth; + var skip = parseBorderSkipped(vm); + var t, r, b, l; + + if (helpers$1.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, + r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, + b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, + l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l + }; +} + +function boundingRects(vm) { + var bounds = getBarBounds(vm); + var width = bounds.right - bounds.left; + var height = bounds.bottom - bounds.top; + var border = parseBorderWidth(vm, width / 2, height / 2); + + return { + outer: { + x: bounds.left, + y: bounds.top, + w: width, + h: height + }, + inner: { + x: bounds.left + border.l, + y: bounds.top + border.t, + w: width - border.l - border.r, + h: height - border.t - border.b + } + }; +} + +function inRange(vm, x, y) { + var skipX = x === null; + var skipY = y === null; + var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm); + + return bounds + && (skipX || x >= bounds.left && x <= bounds.right) + && (skipY || y >= bounds.top && y <= bounds.bottom); +} + +var element_rectangle = core_element.extend({ + _type: 'rectangle', + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var rects = boundingRects(vm); + var outer = rects.outer; + var inner = rects.inner; + + ctx.save(); + + if (outer.w !== inner.w || outer.h !== inner.h) { + ctx.beginPath(); + ctx.strokeStyle = vm.borderColor; + ctx.strokeRect(inner.x, inner.y, inner.w, inner.h); + ctx.fill('evenodd'); + } + + ctx.fillStyle = vm.backgroundColor; + ctx.fillRect(inner.x, inner.y, inner.w, inner.h); + + ctx.restore(); + }, + + height: function() { + var vm = this._view; + return vm.base - vm.y; + }, + + inRange: function(mouseX, mouseY) { + return inRange(this._view, mouseX, mouseY); + }, + + inLabelRange: function(mouseX, mouseY) { + var vm = this._view; + return isVertical(vm) + ? inRange(vm, mouseX, null) + : inRange(vm, null, mouseY); + }, + + inXRange: function(mouseX) { + return inRange(this._view, mouseX, null); + }, + + inYRange: function(mouseY) { + return inRange(this._view, null, mouseY); + }, + + getCenterPoint: function() { + var vm = this._view; + var x, y; + if (isVertical(vm)) { + x = vm.x; + y = (vm.y + vm.base) / 2; + } else { + x = (vm.x + vm.base) / 2; + y = vm.y; + } + + return {x: x, y: y}; + }, + + getArea: function() { + var vm = this._view; + + return isVertical(vm) + ? vm.width * Math.abs(vm.y - vm.base) + : vm.height * Math.abs(vm.x - vm.base); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + } +}); + +var elements = {}; +var Arc = element_arc; +var Line = element_line; +var Point = element_point; +var Rectangle = element_rectangle; +elements.Arc = Arc; +elements.Line = Line; +elements.Point = Point; +elements.Rectangle = Rectangle; + +var deprecated = helpers$1._deprecated; +var valueOrDefault$3 = helpers$1.valueOrDefault; + +core_defaults._set('bar', { + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + offset: true, + gridLines: { + offsetGridLines: true + } + }], + + yAxes: [{ + type: 'linear' + }] + } +}); + +core_defaults._set('global', { + datasets: { + bar: { + categoryPercentage: 0.8, + barPercentage: 0.9 + } + } +}); + +/** + * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. + * @private + */ +function computeMinSampleSize(scale, pixels) { + var min = scale._length; + var prev, curr, i, ilen; + + for (i = 1, ilen = pixels.length; i < ilen; ++i) { + min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1])); + } + + for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) { + curr = scale.getPixelForTick(i); + min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min; + prev = curr; + } + + return min; +} + +/** + * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, + * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This + * mode currently always generates bars equally sized (until we introduce scriptable options?). + * @private + */ +function computeFitCategoryTraits(index, ruler, options) { + var thickness = options.barThickness; + var count = ruler.stackCount; + var curr = ruler.pixels[index]; + var min = helpers$1.isNullOrUndef(thickness) + ? computeMinSampleSize(ruler.scale, ruler.pixels) + : -1; + var size, ratio; + + if (helpers$1.isNullOrUndef(thickness)) { + size = min * options.categoryPercentage; + ratio = options.barPercentage; + } else { + // When bar thickness is enforced, category and bar percentages are ignored. + // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') + // and deprecate barPercentage since this value is ignored when thickness is absolute. + size = thickness * count; + ratio = 1; + } + + return { + chunk: size / count, + ratio: ratio, + start: curr - (size / 2) + }; +} + +/** + * Computes an "optimal" category that globally arranges bars side by side (no gap when + * percentage options are 1), based on the previous and following categories. This mode + * generates bars with different widths when data are not evenly spaced. + * @private + */ +function computeFlexCategoryTraits(index, ruler, options) { + var pixels = ruler.pixels; + var curr = pixels[index]; + var prev = index > 0 ? pixels[index - 1] : null; + var next = index < pixels.length - 1 ? pixels[index + 1] : null; + var percent = options.categoryPercentage; + var start, size; + + if (prev === null) { + // first data: its size is double based on the next point or, + // if it's also the last data, we use the scale size. + prev = curr - (next === null ? ruler.end - ruler.start : next - curr); + } + + if (next === null) { + // last data: its size is also double based on the previous point. + next = curr + curr - prev; + } + + start = curr - (curr - Math.min(prev, next)) / 2 * percent; + size = Math.abs(next - prev) / 2 * percent; + + return { + chunk: size / ruler.stackCount, + ratio: options.barPercentage, + start: start + }; +} + +var controller_bar = core_datasetController.extend({ + + dataElementType: elements.Rectangle, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderSkipped', + 'borderWidth', + 'barPercentage', + 'barThickness', + 'categoryPercentage', + 'maxBarThickness', + 'minBarLength' + ], + + initialize: function() { + var me = this; + var meta, scaleOpts; + + core_datasetController.prototype.initialize.apply(me, arguments); + + meta = me.getMeta(); + meta.stack = me.getDataset().stack; + meta.bar = true; + + scaleOpts = me._getIndexScale().options; + deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage'); + deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness'); + deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage'); + deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength'); + deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness'); + }, + + update: function(reset) { + var me = this; + var rects = me.getMeta().data; + var i, ilen; + + me._ruler = me.getRuler(); + + for (i = 0, ilen = rects.length; i < ilen; ++i) { + me.updateElement(rects[i], i, reset); + } + }, + + updateElement: function(rectangle, index, reset) { + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + var options = me._resolveDataElementOptions(rectangle, index); + + rectangle._xScale = me.getScaleForId(meta.xAxisID); + rectangle._yScale = me.getScaleForId(meta.yAxisID); + rectangle._datasetIndex = me.index; + rectangle._index = index; + rectangle._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderSkipped: options.borderSkipped, + borderWidth: options.borderWidth, + datasetLabel: dataset.label, + label: me.chart.data.labels[index] + }; + + if (helpers$1.isArray(dataset.data[index])) { + rectangle._model.borderSkipped = null; + } + + me._updateElementGeometry(rectangle, index, reset, options); + + rectangle.pivot(); + }, + + /** + * @private + */ + _updateElementGeometry: function(rectangle, index, reset, options) { + var me = this; + var model = rectangle._model; + var vscale = me._getValueScale(); + var base = vscale.getBasePixel(); + var horizontal = vscale.isHorizontal(); + var ruler = me._ruler || me.getRuler(); + var vpixels = me.calculateBarValuePixels(me.index, index, options); + var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options); + + model.horizontal = horizontal; + model.base = reset ? base : vpixels.base; + model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; + model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; + model.height = horizontal ? ipixels.size : undefined; + model.width = horizontal ? undefined : ipixels.size; + }, + + /** + * Returns the stacks based on groups and bar visibility. + * @param {number} [last] - The dataset index + * @returns {string[]} The list of stack IDs + * @private + */ + _getStacks: function(last) { + var me = this; + var scale = me._getIndexScale(); + var metasets = scale._getMatchingVisibleMetas(me._type); + var stacked = scale.options.stacked; + var ilen = metasets.length; + var stacks = []; + var i, meta; + + for (i = 0; i < ilen; ++i) { + meta = metasets[i]; + // stacked | meta.stack + // | found | not found | undefined + // false | x | x | x + // true | | x | + // undefined | | x | x + if (stacked === false || stacks.indexOf(meta.stack) === -1 || + (stacked === undefined && meta.stack === undefined)) { + stacks.push(meta.stack); + } + if (meta.index === last) { + break; + } + } + + return stacks; + }, + + /** + * Returns the effective number of stacks based on groups and bar visibility. + * @private + */ + getStackCount: function() { + return this._getStacks().length; + }, + + /** + * Returns the stack index for the given dataset based on groups and bar visibility. + * @param {number} [datasetIndex] - The dataset index + * @param {string} [name] - The stack name to find + * @returns {number} The stack index + * @private + */ + getStackIndex: function(datasetIndex, name) { + var stacks = this._getStacks(datasetIndex); + var index = (name !== undefined) + ? stacks.indexOf(name) + : -1; // indexOf returns -1 if element is not present + + return (index === -1) + ? stacks.length - 1 + : index; + }, + + /** + * @private + */ + getRuler: function() { + var me = this; + var scale = me._getIndexScale(); + var pixels = []; + var i, ilen; + + for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { + pixels.push(scale.getPixelForValue(null, i, me.index)); + } + + return { + pixels: pixels, + start: scale._startPixel, + end: scale._endPixel, + stackCount: me.getStackCount(), + scale: scale + }; + }, + + /** + * Note: pixel values are not clamped to the scale area. + * @private + */ + calculateBarValuePixels: function(datasetIndex, index, options) { + var me = this; + var chart = me.chart; + var scale = me._getValueScale(); + var isHorizontal = scale.isHorizontal(); + var datasets = chart.data.datasets; + var metasets = scale._getMatchingVisibleMetas(me._type); + var value = scale._parseValue(datasets[datasetIndex].data[index]); + var minBarLength = options.minBarLength; + var stacked = scale.options.stacked; + var stack = me.getMeta().stack; + var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max; + var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max; + var ilen = metasets.length; + var i, imeta, ivalue, base, head, size, stackLength; + + if (stacked || (stacked === undefined && stack !== undefined)) { + for (i = 0; i < ilen; ++i) { + imeta = metasets[i]; + + if (imeta.index === datasetIndex) { + break; + } + + if (imeta.stack === stack) { + stackLength = scale._parseValue(datasets[imeta.index].data[index]); + ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min; + + if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) { + start += ivalue; + } + } + } + } + + base = scale.getPixelForValue(start); + head = scale.getPixelForValue(start + length); + size = head - base; + + if (minBarLength !== undefined && Math.abs(size) < minBarLength) { + size = minBarLength; + if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) { + head = base - minBarLength; + } else { + head = base + minBarLength; + } + } + + return { + size: size, + base: base, + head: head, + center: head + size / 2 + }; + }, + + /** + * @private + */ + calculateBarIndexPixels: function(datasetIndex, index, ruler, options) { + var me = this; + var range = options.barThickness === 'flex' + ? computeFlexCategoryTraits(index, ruler, options) + : computeFitCategoryTraits(index, ruler, options); + + var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); + var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); + var size = Math.min( + valueOrDefault$3(options.maxBarThickness, Infinity), + range.chunk * range.ratio); + + return { + base: center - size / 2, + head: center + size / 2, + center: center, + size: size + }; + }, + + draw: function() { + var me = this; + var chart = me.chart; + var scale = me._getValueScale(); + var rects = me.getMeta().data; + var dataset = me.getDataset(); + var ilen = rects.length; + var i = 0; + + helpers$1.canvas.clipArea(chart.ctx, chart.chartArea); + + for (; i < ilen; ++i) { + var val = scale._parseValue(dataset.data[i]); + if (!isNaN(val.min) && !isNaN(val.max)) { + rects[i].draw(); + } + } + + helpers$1.canvas.unclipArea(chart.ctx); + }, + + /** + * @private + */ + _resolveDataElementOptions: function() { + var me = this; + var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments)); + var indexOpts = me._getIndexScale().options; + var valueOpts = me._getValueScale().options; + + values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage); + values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness); + values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage); + values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness); + values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength); + + return values; + } + +}); + +var valueOrDefault$4 = helpers$1.valueOrDefault; +var resolve$1 = helpers$1.options.resolve; + +core_defaults._set('bubble', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + type: 'linear', // bubble should probably use a linear scale by default + position: 'bottom', + id: 'x-axis-0' // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: 'linear', + position: 'left', + id: 'y-axis-0' + }] + }, + + tooltips: { + callbacks: { + title: function() { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + var dataPoint = data.datasets[item.datasetIndex].data[item.index]; + return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; + } + } + } +}); + +var controller_bubble = core_datasetController.extend({ + /** + * @protected + */ + dataElementType: elements.Point, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + 'hoverRadius', + 'hitRadius', + 'pointStyle', + 'rotation' + ], + + /** + * @protected + */ + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var points = meta.data; + + // Update Points + helpers$1.each(points, function(point, index) { + me.updateElement(point, index, reset); + }); + }, + + /** + * @protected + */ + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); + var options = me._resolveDataElementOptions(point, index); + var data = me.getDataset().data[index]; + var dsIndex = me.index; + + var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); + var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); + + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = dsIndex; + point._index = index; + point._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + hitRadius: options.hitRadius, + pointStyle: options.pointStyle, + rotation: options.rotation, + radius: reset ? 0 : options.radius, + skip: custom.skip || isNaN(x) || isNaN(y), + x: x, + y: y, + }; + + point.pivot(); + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth); + model.radius = options.radius + options.hoverRadius; + }, + + /** + * @private + */ + _resolveDataElementOptions: function(point, index) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var custom = point.custom || {}; + var data = dataset.data[index] || {}; + var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments); + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + // In case values were cached (and thus frozen), we need to clone the values + if (me._cachedDataOpts === values) { + values = helpers$1.extend({}, values); + } + + // Custom radius resolution + values.radius = resolve$1([ + custom.radius, + data.r, + me._config.radius, + chart.options.elements.point.radius + ], context, index); + + return values; + } +}); + +var valueOrDefault$5 = helpers$1.valueOrDefault; + +var PI$1 = Math.PI; +var DOUBLE_PI$1 = PI$1 * 2; +var HALF_PI$1 = PI$1 / 2; + +core_defaults._set('doughnut', { + animation: { + // Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + // Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false + }, + hover: { + mode: 'single' + }, + legendCallback: function(chart) { + var list = document.createElement('ul'); + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + if (datasets.length) { + for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; + if (labels[i]) { + listItem.appendChild(document.createTextNode(labels[i])); + } + } + } + + return list.outerHTML; + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + // toggle visibility of index if exists + if (meta.data[index]) { + meta.data[index].hidden = !meta.data[index].hidden; + } + } + + chart.update(); + } + }, + + // The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, + + // The rotation of the chart, where the first data arc begins. + rotation: -HALF_PI$1, + + // The total circumference of the chart. + circumference: DOUBLE_PI$1, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + var dataLabel = data.labels[tooltipItem.index]; + var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + + if (helpers$1.isArray(dataLabel)) { + // show value on first line of multiline label + // need to clone because we are changing the value + dataLabel = dataLabel.slice(); + dataLabel[0] += value; + } else { + dataLabel += value; + } + + return dataLabel; + } + } + } +}); + +var controller_doughnut = core_datasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + ], + + // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly + getRingIndex: function(datasetIndex) { + var ringIndex = 0; + + for (var j = 0; j < datasetIndex; ++j) { + if (this.chart.isDatasetVisible(j)) { + ++ringIndex; + } + } + + return ringIndex; + }, + + update: function(reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var ratioX = 1; + var ratioY = 1; + var offsetX = 0; + var offsetY = 0; + var meta = me.getMeta(); + var arcs = meta.data; + var cutout = opts.cutoutPercentage / 100 || 0; + var circumference = opts.circumference; + var chartWeight = me._getRingWeight(me.index); + var maxWidth, maxHeight, i, ilen; + + // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc + if (circumference < DOUBLE_PI$1) { + var startAngle = opts.rotation % DOUBLE_PI$1; + startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0; + var endAngle = startAngle + circumference; + var startX = Math.cos(startAngle); + var startY = Math.sin(startAngle); + var endX = Math.cos(endAngle); + var endY = Math.sin(endAngle); + var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI$1; + var contains90 = (startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1) || endAngle >= DOUBLE_PI$1 + HALF_PI$1; + var contains180 = startAngle === -PI$1 || endAngle >= PI$1; + var contains270 = (startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1) || endAngle >= PI$1 + HALF_PI$1; + var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout); + var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout); + var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout); + var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout); + ratioX = (maxX - minX) / 2; + ratioY = (maxY - minY) / 2; + offsetX = -(maxX + minX) / 2; + offsetY = -(maxY + minY) / 2; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); + } + + chart.borderWidth = me.getMaxBorderWidth(); + maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX; + maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY; + chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); + chart.innerRadius = Math.max(chart.outerRadius * cutout, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1); + chart.offsetX = offsetX * chart.outerRadius; + chart.offsetY = offsetY * chart.outerRadius; + + meta.total = me.calculateTotal(); + + me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index); + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0); + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + me.updateElement(arcs[i], i, reset); + } + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var animationOpts = opts.animation; + var centerX = (chartArea.left + chartArea.right) / 2; + var centerY = (chartArea.top + chartArea.bottom) / 2; + var startAngle = opts.rotation; // non reset case handled later + var endAngle = opts.rotation; // non reset case handled later + var dataset = me.getDataset(); + var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1); + var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; + var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; + var options = arc._options || {}; + + helpers$1.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + + // Desired view properties + _model: { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + borderAlign: options.borderAlign, + x: centerX + chart.offsetX, + y: centerY + chart.offsetY, + startAngle: startAngle, + endAngle: endAngle, + circumference: circumference, + outerRadius: outerRadius, + innerRadius: innerRadius, + label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) + } + }); + + var model = arc._model; + + // Set correct angles if not resetting + if (!reset || !animationOpts.animateRotate) { + if (index === 0) { + model.startAngle = opts.rotation; + } else { + model.startAngle = me.getMeta().data[index - 1]._model.endAngle; + } + + model.endAngle = model.startAngle + model.circumference; + } + + arc.pivot(); + }, + + calculateTotal: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var total = 0; + var value; + + helpers$1.each(meta.data, function(element, index) { + value = dataset.data[index]; + if (!isNaN(value) && !element.hidden) { + total += Math.abs(value); + } + }); + + /* if (total === 0) { + total = NaN; + }*/ + + return total; + }, + + calculateCircumference: function(value) { + var total = this.getMeta().total; + if (total > 0 && !isNaN(value)) { + return DOUBLE_PI$1 * (Math.abs(value) / total); + } + return 0; + }, + + // gets the max border or hover width to properly scale pie charts + getMaxBorderWidth: function(arcs) { + var me = this; + var max = 0; + var chart = me.chart; + var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth; + + if (!arcs) { + // Find the outmost visible dataset + for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + meta = chart.getDatasetMeta(i); + arcs = meta.data; + if (i !== me.index) { + controller = meta.controller; + } + break; + } + } + } + + if (!arcs) { + return 0; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arc = arcs[i]; + if (controller) { + controller._configure(); + options = controller._resolveDataElementOptions(arc, i); + } else { + options = arc._options; + } + if (options.borderAlign !== 'inner') { + borderWidth = options.borderWidth; + hoverWidth = options.hoverBorderWidth; + + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; + } + } + return max; + }, + + /** + * @protected + */ + setHoverStyle: function(arc) { + var model = arc._model; + var options = arc._options; + var getHoverColor = helpers$1.getHoverColor; + + arc.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + }; + + model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth); + }, + + /** + * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly + * @private + */ + _getRingWeightOffset: function(datasetIndex) { + var ringWeightOffset = 0; + + for (var i = 0; i < datasetIndex; ++i) { + if (this.chart.isDatasetVisible(i)) { + ringWeightOffset += this._getRingWeight(i); + } + } + + return ringWeightOffset; + }, + + /** + * @private + */ + _getRingWeight: function(dataSetIndex) { + return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0); + }, + + /** + * Returns the sum of all visibile data set weights. This value can be 0. + * @private + */ + _getVisibleDatasetWeightTotal: function() { + return this._getRingWeightOffset(this.chart.data.datasets.length); + } +}); + +core_defaults._set('horizontalBar', { + hover: { + mode: 'index', + axis: 'y' + }, + + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + + yAxes: [{ + type: 'category', + position: 'left', + offset: true, + gridLines: { + offsetGridLines: true + } + }] + }, + + elements: { + rectangle: { + borderSkipped: 'left' + } + }, + + tooltips: { + mode: 'index', + axis: 'y' + } +}); + +core_defaults._set('global', { + datasets: { + horizontalBar: { + categoryPercentage: 0.8, + barPercentage: 0.9 + } + } +}); + +var controller_horizontalBar = controller_bar.extend({ + /** + * @private + */ + _getValueScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.getMeta().yAxisID; + } +}); + +var valueOrDefault$6 = helpers$1.valueOrDefault; +var resolve$2 = helpers$1.options.resolve; +var isPointInArea = helpers$1.canvas._isPointInArea; + +core_defaults._set('line', { + showLines: true, + spanGaps: false, + + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + id: 'x-axis-0' + }], + yAxes: [{ + type: 'linear', + id: 'y-axis-0' + }] + } +}); + +function scaleClip(scale, halfBorderWidth) { + var tickOpts = scale && scale.options.ticks || {}; + var reverse = tickOpts.reverse; + var min = tickOpts.min === undefined ? halfBorderWidth : 0; + var max = tickOpts.max === undefined ? halfBorderWidth : 0; + return { + start: reverse ? max : min, + end: reverse ? min : max + }; +} + +function defaultClip(xScale, yScale, borderWidth) { + var halfBorderWidth = borderWidth / 2; + var x = scaleClip(xScale, halfBorderWidth); + var y = scaleClip(yScale, halfBorderWidth); + + return { + top: y.end, + right: x.end, + bottom: y.start, + left: x.start + }; +} + +function toClip(value) { + var t, r, b, l; + + if (helpers$1.isObject(value)) { + t = value.top; + r = value.right; + b = value.bottom; + l = value.left; + } else { + t = r = b = l = value; + } + + return { + top: t, + right: r, + bottom: b, + left: l + }; +} + + +var controller_line = core_datasetController.extend({ + + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + /** + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth', + 'cubicInterpolationMode', + 'fill' + ], + + /** + * @private + */ + _dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var options = me.chart.options; + var config = me._config; + var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines); + var i, ilen; + + me._xScale = me.getScaleForId(meta.xAxisID); + me._yScale = me.getScaleForId(meta.yAxisID); + + // Update Line + if (showLine) { + // Compatibility: If the properties are defined with only the old name, use those values + if (config.tension !== undefined && config.lineTension === undefined) { + config.lineTension = config.tension; + } + + // Utility + line._scale = me._yScale; + line._datasetIndex = me.index; + // Data + line._children = points; + // Model + line._model = me._resolveDatasetElementOptions(line); + + line.pivot(); + } + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + if (showLine && line._model.tension !== 0) { + me.updateBezierControlPoints(); + } + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var dataset = me.getDataset(); + var datasetIndex = me.index; + var value = dataset.data[index]; + var xScale = me._xScale; + var yScale = me._yScale; + var lineModel = meta.dataset._model; + var x, y; + + var options = me._resolveDataElementOptions(point, index); + + x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); + y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); + + // Utility + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = datasetIndex; + point._index = index; + + // Desired view properties + point._model = { + x: x, + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: options.radius, + pointStyle: options.pointStyle, + rotation: options.rotation, + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0), + steppedLine: lineModel ? lineModel.steppedLine : false, + // Tooltip + hitRadius: options.hitRadius + }; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function(element) { + var me = this; + var config = me._config; + var custom = element.custom || {}; + var options = me.chart.options; + var lineOptions = options.elements.line; + var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); + + // The default behavior of lines is to break at null values, according + // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 + // This option gives lines the ability to span gaps + values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension); + values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]); + values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth))); + + return values; + }, + + calculatePointY: function(value, index, datasetIndex) { + var me = this; + var chart = me.chart; + var yScale = me._yScale; + var sumPos = 0; + var sumNeg = 0; + var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen; + + if (yScale.options.stacked) { + rightValue = +yScale.getRightValue(value); + metasets = chart._getSortedVisibleDatasetMetas(); + ilen = metasets.length; + + for (i = 0; i < ilen; ++i) { + dsMeta = metasets[i]; + if (dsMeta.index === datasetIndex) { + break; + } + + ds = chart.data.datasets[dsMeta.index]; + if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) { + stackedRightValue = +yScale.getRightValue(ds.data[index]); + if (stackedRightValue < 0) { + sumNeg += stackedRightValue || 0; + } else { + sumPos += stackedRightValue || 0; + } + } + } + + if (rightValue < 0) { + return yScale.getPixelForValue(sumNeg + rightValue); + } + return yScale.getPixelForValue(sumPos + rightValue); + } + return yScale.getPixelForValue(value); + }, + + updateBezierControlPoints: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var lineModel = meta.dataset._model; + var area = chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (lineModel.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + if (lineModel.cubicInterpolationMode === 'monotone') { + helpers$1.splineCurveMonotone(points); + } else { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + controlPoints = helpers$1.splineCurve( + helpers$1.previousItem(points, i)._model, + model, + helpers$1.nextItem(points, i)._model, + lineModel.tension + ); + model.controlPointPreviousX = controlPoints.previous.x; + model.controlPointPreviousY = controlPoints.previous.y; + model.controlPointNextX = controlPoints.next.x; + model.controlPointNextY = controlPoints.next.y; + } + } + + if (chart.options.elements.line.capBezierPoints) { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + if (isPointInArea(model, area)) { + if (i > 0 && isPointInArea(points[i - 1]._model, area)) { + model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); + model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); + } + if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) { + model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); + model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); + } + } + } + } + }, + + draw: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var points = meta.data || []; + var area = chart.chartArea; + var canvas = chart.canvas; + var i = 0; + var ilen = points.length; + var clip; + + if (me._showLine) { + clip = meta.dataset._model.clip; + + helpers$1.canvas.clipArea(chart.ctx, { + left: clip.left === false ? 0 : area.left - clip.left, + right: clip.right === false ? canvas.width : area.right + clip.right, + top: clip.top === false ? 0 : area.top - clip.top, + bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom + }); + + meta.dataset.draw(); + + helpers$1.canvas.unclipArea(chart.ctx); + } + + // Draw the points + for (; i < ilen; ++i) { + points[i].draw(area); + } + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = "rgba(255,0,0,1.)";//valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = 1;//valueOrDefault$6(options.hoverBorderWidth, options.borderWidth); + model.radius = 2;//valueOrDefault$6(options.hoverRadius, options.radius); + }, +}); + +var resolve$3 = helpers$1.options.resolve; + +core_defaults._set('polarArea', { + scale: { + type: 'radialLinear', + angleLines: { + display: false + }, + gridLines: { + circular: true + }, + pointLabels: { + display: false + }, + ticks: { + beginAtZero: true + } + }, + + // Boolean - Whether to animate the rotation of the chart + animation: { + animateRotate: true, + animateScale: true + }, + + startAngle: -0.5 * Math.PI, + legendCallback: function(chart) { + var list = document.createElement('ul'); + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + if (datasets.length) { + for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; + if (labels[i]) { + listItem.appendChild(document.createTextNode(labels[i])); + } + } + } + + return list.outerHTML; + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + meta.data[index].hidden = !meta.data[index].hidden; + } + + chart.update(); + } + }, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(item, data) { + return data.labels[item.index] + ': ' + item.yLabel; + } + } + } +}); + +var controller_polarArea = core_datasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + ], + + /** + * @private + */ + _getIndexScaleId: function() { + return this.chart.scale.id; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.chart.scale.id; + }, + + update: function(reset) { + var me = this; + var dataset = me.getDataset(); + var meta = me.getMeta(); + var start = me.chart.options.startAngle || 0; + var starts = me._starts = []; + var angles = me._angles = []; + var arcs = meta.data; + var i, ilen, angle; + + me._updateRadius(); + + meta.count = me.countVisibleElements(); + + for (i = 0, ilen = dataset.data.length; i < ilen; i++) { + starts[i] = start; + angle = me._computeAngle(i); + angles[i] = angle; + start += angle; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); + me.updateElement(arcs[i], i, reset); + } + }, + + /** + * @private + */ + _updateRadius: function() { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + + chart.outerRadius = Math.max(minSize / 2, 0); + chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); + me.innerRadius = me.outerRadius - chart.radiusLength; + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var opts = chart.options; + var animationOpts = opts.animation; + var scale = chart.scale; + var labels = chart.data.labels; + + var centerX = scale.xCenter; + var centerY = scale.yCenter; + + // var negHalfPI = -0.5 * Math.PI; + var datasetStartAngle = opts.startAngle; + var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var startAngle = me._starts[index]; + var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); + + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var options = arc._options || {}; + + helpers$1.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + borderAlign: options.borderAlign, + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: reset ? resetRadius : distance, + startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, + endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, + label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index]) + } + }); + + arc.pivot(); + }, + + countVisibleElements: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; + + helpers$1.each(meta.data, function(element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; + } + }); + + return count; + }, + + /** + * @protected + */ + setHoverStyle: function(arc) { + var model = arc._model; + var options = arc._options; + var getHoverColor = helpers$1.getHoverColor; + var valueOrDefault = helpers$1.valueOrDefault; + + arc.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + }; + + model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth); + }, + + /** + * @private + */ + _computeAngle: function(index) { + var me = this; + var count = this.getMeta().count; + var dataset = me.getDataset(); + var meta = me.getMeta(); + + if (isNaN(dataset.data[index]) || meta.data[index].hidden) { + return 0; + } + + // Scriptable options + var context = { + chart: me.chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + return resolve$3([ + me.chart.options.elements.arc.angle, + (2 * Math.PI) / count + ], context, index); + } +}); + +core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut)); +core_defaults._set('pie', { + cutoutPercentage: 0 +}); + +// Pie charts are Doughnut chart with different defaults +var controller_pie = controller_doughnut; + +var valueOrDefault$7 = helpers$1.valueOrDefault; + +core_defaults._set('radar', { + spanGaps: false, + scale: { + type: 'radialLinear' + }, + elements: { + line: { + fill: 'start', + tension: 0 // no bezier in radar + } + } +}); + +var controller_radar = core_datasetController.extend({ + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderWidth', + 'borderColor', + 'borderCapStyle', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'fill' + ], + + /** + * @private + */ + _dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.chart.scale.id; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.chart.scale.id; + }, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var scale = me.chart.scale; + var config = me._config; + var i, ilen; + + // Compatibility: If the properties are defined with only the old name, use those values + if (config.tension !== undefined && config.lineTension === undefined) { + config.lineTension = config.tension; + } + + // Utility + line._scale = scale; + line._datasetIndex = me.index; + // Data + line._children = points; + line._loop = true; + // Model + line._model = me._resolveDatasetElementOptions(line); + + line.pivot(); + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + // Update bezier control points + me.updateBezierControlPoints(); + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + updateElement: function(point, index, reset) { + var me = this; + var custom = point.custom || {}; + var dataset = me.getDataset(); + var scale = me.chart.scale; + var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); + var options = me._resolveDataElementOptions(point, index); + var lineModel = me.getMeta().dataset._model; + var x = reset ? scale.xCenter : pointPosition.x; + var y = reset ? scale.yCenter : pointPosition.y; + + // Utility + point._scale = scale; + point._options = options; + point._datasetIndex = me.index; + point._index = index; + + // Desired view properties + point._model = { + x: x, // value not used in dataset scale, but we want a consistent API between scales + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: options.radius, + pointStyle: options.pointStyle, + rotation: options.rotation, + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0), + + // Tooltip + hitRadius: options.hitRadius + }; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function() { + var me = this; + var config = me._config; + var options = me.chart.options; + var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); + + values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension); + + return values; + }, + + updateBezierControlPoints: function() { + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (meta.dataset._model.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + controlPoints = helpers$1.splineCurve( + helpers$1.previousItem(points, i, true)._model, + model, + helpers$1.nextItem(points, i, true)._model, + model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); + model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); + model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); + model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); + } + }, + + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth); + model.radius = valueOrDefault$7(options.hoverRadius, options.radius); + } +}); + +core_defaults._set('scatter', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + id: 'x-axis-1', // need an ID so datasets can reference the scale + type: 'linear', // scatter should not use a category axis + position: 'bottom' + }], + yAxes: [{ + id: 'y-axis-1', + type: 'linear', + position: 'left' + }] + }, + + tooltips: { + callbacks: { + title: function() { + return ''; // doesn't make sense for scatter since data are formatted as a point + }, + label: function(item) { + return '(' + item.xLabel + ', ' + item.yLabel + ')'; + } + } + } +}); + +core_defaults._set('global', { + datasets: { + scatter: { + showLine: false + } + } +}); + +// Scatter charts use line controllers +var controller_scatter = controller_line; + +// NOTE export a map in which the key represents the controller type, not +// the class, and so must be CamelCase in order to be correctly retrieved +// by the controller in core.controller.js (`controllers[meta.type]`). + +var controllers = { + bar: controller_bar, + bubble: controller_bubble, + doughnut: controller_doughnut, + horizontalBar: controller_horizontalBar, + line: controller_line, + polarArea: controller_polarArea, + pie: controller_pie, + radar: controller_radar, + scatter: controller_scatter +}; + +/** + * Helper function to get relative position for an event + * @param {Event|IEvent} event - The event to get the position for + * @param {Chart} chart - The chart + * @returns {object} the event position + */ +function getRelativePosition(e, chart) { + if (e.native) { + return { + x: e.x, + y: e.y + }; + } + + return helpers$1.getRelativePosition(e, chart); +} + +/** + * Helper function to traverse all of the visible elements in the chart + * @param {Chart} chart - the chart + * @param {function} handler - the callback to execute for each visible item + */ +function parseVisibleItems(chart, handler) { + var metasets = chart._getSortedVisibleDatasetMetas(); + var metadata, i, j, ilen, jlen, element; + + for (i = 0, ilen = metasets.length; i < ilen; ++i) { + metadata = metasets[i].data; + for (j = 0, jlen = metadata.length; j < jlen; ++j) { + element = metadata[j]; + if (!element._view.skip) { + handler(element); + } + } + } +} + +/** + * Helper function to get the items that intersect the event position + * @param {ChartElement[]} items - elements to filter + * @param {object} position - the point to be nearest to + * @return {ChartElement[]} the nearest items + */ +function getIntersectItems(chart, position) { + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + } + }); + + return elements; +} + +/** + * Helper function to get the items nearest to the event position considering all visible items in teh chart + * @param {Chart} chart - the chart to look at elements from + * @param {object} position - the point to be nearest to + * @param {boolean} intersect - if true, only consider items that intersect the position + * @param {function} distanceMetric - function to provide the distance between points + * @return {ChartElement[]} the nearest items + */ +function getNearestItems(chart, position, intersect, distanceMetric) { + var minDistance = Number.POSITIVE_INFINITY; + var nearestItems = []; + + parseVisibleItems(chart, function(element) { + if (intersect && !element.inRange(position.x, position.y)) { + return; + } + + var center = element.getCenterPoint(); + var distance = distanceMetric(position, center); + if (distance < minDistance) { + nearestItems = [element]; + minDistance = distance; + } else if (distance === minDistance) { + // Can have multiple items at the same distance in which case we sort by size + nearestItems.push(element); + } + }); + + return nearestItems; +} + +/** + * Get a distance metric function for two points based on the + * axis mode setting + * @param {string} axis - the axis mode. x|y|xy + */ +function getDistanceMetricForAxis(axis) { + var useX = axis.indexOf('x') !== -1; + var useY = axis.indexOf('y') !== -1; + + return function(pt1, pt2) { + var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; + var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; + return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); + }; +} + +function indexMode(chart, e, options) { + var position = getRelativePosition(e, chart); + // Default axis for index mode is 'x' to match old behaviour + options.axis = options.axis || 'x'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + var elements = []; + + if (!items.length) { + return []; + } + + chart._getSortedVisibleDatasetMetas().forEach(function(meta) { + var element = meta.data[items[0]._index]; + + // don't count items that are skipped (null data) + if (element && !element._view.skip) { + elements.push(element); + } + }); + + return elements; +} + +/** + * @interface IInteractionOptions + */ +/** + * If true, only consider items that intersect the point + * @name IInterfaceOptions#boolean + * @type Boolean + */ + +/** + * Contains interaction related functions + * @namespace Chart.Interaction + */ +var core_interaction = { + // Helper function for different modes + modes: { + single: function(chart, e) { + var position = getRelativePosition(e, chart); + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + return elements; + } + }); + + return elements.slice(0, 1); + }, + + /** + * @function Chart.Interaction.modes.label + * @deprecated since version 2.4.0 + * @todo remove at version 3 + * @private + */ + label: indexMode, + + /** + * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item + * @function Chart.Interaction.modes.index + * @since v2.4.0 + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + index: indexMode, + + /** + * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect is false, we find the nearest item and return the items in that dataset + * @function Chart.Interaction.modes.dataset + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + dataset: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + + if (items.length > 0) { + items = chart.getDatasetMeta(items[0]._datasetIndex).data; + } + + return items; + }, + + /** + * @function Chart.Interaction.modes.x-axis + * @deprecated since version 2.4.0. Use index mode and intersect == true + * @todo remove at version 3 + * @private + */ + 'x-axis': function(chart, e) { + return indexMode(chart, e, {intersect: false}); + }, + + /** + * Point mode returns all elements that hit test based on the event position + * of the event + * @function Chart.Interaction.modes.intersect + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + point: function(chart, e) { + var position = getRelativePosition(e, chart); + return getIntersectItems(chart, position); + }, + + /** + * nearest mode returns the element closest to the point + * @function Chart.Interaction.modes.intersect + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + nearest: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + return getNearestItems(chart, position, options.intersect, distanceMetric); + }, + + /** + * x mode returns the elements that hit-test at the current x coordinate + * @function Chart.Interaction.modes.x + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + x: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inXRange(position.x)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + }, + + /** + * y mode returns the elements that hit-test at the current y coordinate + * @function Chart.Interaction.modes.y + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + y: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inYRange(position.y)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + } + } +}; + +var extend = helpers$1.extend; + +function filterByPosition(array, position) { + return helpers$1.where(array, function(v) { + return v.pos === position; + }); +} + +function sortByWeight(array, reverse) { + return array.sort(function(a, b) { + var v0 = reverse ? b : a; + var v1 = reverse ? a : b; + return v0.weight === v1.weight ? + v0.index - v1.index : + v0.weight - v1.weight; + }); +} + +function wrapBoxes(boxes) { + var layoutBoxes = []; + var i, ilen, box; + + for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { + box = boxes[i]; + layoutBoxes.push({ + index: i, + box: box, + pos: box.position, + horizontal: box.isHorizontal(), + weight: box.weight + }); + } + return layoutBoxes; +} + +function setLayoutDims(layouts, params) { + var i, ilen, layout; + for (i = 0, ilen = layouts.length; i < ilen; ++i) { + layout = layouts[i]; + // store width used instead of chartArea.w in fitBoxes + layout.width = layout.horizontal + ? layout.box.fullWidth && params.availableWidth + : params.vBoxMaxWidth; + // store height used instead of chartArea.h in fitBoxes + layout.height = layout.horizontal && params.hBoxMaxHeight; + } +} + +function buildLayoutBoxes(boxes) { + var layoutBoxes = wrapBoxes(boxes); + var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); + var right = sortByWeight(filterByPosition(layoutBoxes, 'right')); + var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); + var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); + + return { + leftAndTop: left.concat(top), + rightAndBottom: right.concat(bottom), + chartArea: filterByPosition(layoutBoxes, 'chartArea'), + vertical: left.concat(right), + horizontal: top.concat(bottom) + }; +} + +function getCombinedMax(maxPadding, chartArea, a, b) { + return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); +} + +function updateDims(chartArea, params, layout) { + var box = layout.box; + var maxPadding = chartArea.maxPadding; + var newWidth, newHeight; + + if (layout.size) { + // this layout was already counted for, lets first reduce old size + chartArea[layout.pos] -= layout.size; + } + layout.size = layout.horizontal ? box.height : box.width; + chartArea[layout.pos] += layout.size; + + if (box.getPadding) { + var boxPadding = box.getPadding(); + maxPadding.top = Math.max(maxPadding.top, boxPadding.top); + maxPadding.left = Math.max(maxPadding.left, boxPadding.left); + maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); + maxPadding.right = Math.max(maxPadding.right, boxPadding.right); + } + + newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'); + newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'); + + if (newWidth !== chartArea.w || newHeight !== chartArea.h) { + chartArea.w = newWidth; + chartArea.h = newHeight; + + // return true if chart area changed in layout's direction + return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h; + } +} + +function handleMaxPadding(chartArea) { + var maxPadding = chartArea.maxPadding; + + function updatePos(pos) { + var change = Math.max(maxPadding[pos] - chartArea[pos], 0); + chartArea[pos] += change; + return change; + } + chartArea.y += updatePos('top'); + chartArea.x += updatePos('left'); + updatePos('right'); + updatePos('bottom'); +} + +function getMargins(horizontal, chartArea) { + var maxPadding = chartArea.maxPadding; + + function marginForPositions(positions) { + var margin = {left: 0, top: 0, right: 0, bottom: 0}; + positions.forEach(function(pos) { + margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); + }); + return margin; + } + + return horizontal + ? marginForPositions(['left', 'right']) + : marginForPositions(['top', 'bottom']); +} + +function fitBoxes(boxes, chartArea, params) { + var refitBoxes = []; + var i, ilen, layout, box, refit, changed; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + + box.update( + layout.width || chartArea.w, + layout.height || chartArea.h, + getMargins(layout.horizontal, chartArea) + ); + if (updateDims(chartArea, params, layout)) { + changed = true; + if (refitBoxes.length) { + // Dimensions changed and there were non full width boxes before this + // -> we have to refit those + refit = true; + } + } + if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case + refitBoxes.push(layout); + } + } + + return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed; +} + +function placeBoxes(boxes, chartArea, params) { + var userPadding = params.padding; + var x = chartArea.x; + var y = chartArea.y; + var i, ilen, layout, box; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + if (layout.horizontal) { + box.left = box.fullWidth ? userPadding.left : chartArea.left; + box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; + box.top = y; + box.bottom = y + box.height; + box.width = box.right - box.left; + y = box.bottom; + } else { + box.left = x; + box.right = x + box.width; + box.top = chartArea.top; + box.bottom = chartArea.top + chartArea.h; + box.height = box.bottom - box.top; + x = box.right; + } + } + + chartArea.x = x; + chartArea.y = y; +} + +core_defaults._set('global', { + layout: { + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } +}); + +/** + * @interface ILayoutItem + * @prop {string} position - The position of the item in the chart layout. Possible values are + * 'left', 'top', 'right', 'bottom', and 'chartArea' + * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area + * @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down + * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) + * @prop {function} update - Takes two parameters: width and height. Returns size of item + * @prop {function} getPadding - Returns an object with padding on the edges + * @prop {number} width - Width of item. Must be valid after update() + * @prop {number} height - Height of item. Must be valid after update() + * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update + * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update + * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update + * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update + */ + +// The layout service is very self explanatory. It's responsible for the layout within a chart. +// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need +// It is this service's responsibility of carrying out that layout. +var core_layouts = { + defaults: {}, + + /** + * Register a box to a chart. + * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. + * @param {Chart} chart - the chart to use + * @param {ILayoutItem} item - the item to add to be layed out + */ + addBox: function(chart, item) { + if (!chart.boxes) { + chart.boxes = []; + } + + // initialize item with default values + item.fullWidth = item.fullWidth || false; + item.position = item.position || 'top'; + item.weight = item.weight || 0; + item._layers = item._layers || function() { + return [{ + z: 0, + draw: function() { + item.draw.apply(item, arguments); + } + }]; + }; + + chart.boxes.push(item); + }, + + /** + * Remove a layoutItem from a chart + * @param {Chart} chart - the chart to remove the box from + * @param {ILayoutItem} layoutItem - the item to remove from the layout + */ + removeBox: function(chart, layoutItem) { + var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; + if (index !== -1) { + chart.boxes.splice(index, 1); + } + }, + + /** + * Sets (or updates) options on the given `item`. + * @param {Chart} chart - the chart in which the item lives (or will be added to) + * @param {ILayoutItem} item - the item to configure with the given options + * @param {object} options - the new item options. + */ + configure: function(chart, item, options) { + var props = ['fullWidth', 'position', 'weight']; + var ilen = props.length; + var i = 0; + var prop; + + for (; i < ilen; ++i) { + prop = props[i]; + if (options.hasOwnProperty(prop)) { + item[prop] = options[prop]; + } + } + }, + + /** + * Fits boxes of the given chart into the given size by having each box measure itself + * then running a fitting algorithm + * @param {Chart} chart - the chart + * @param {number} width - the width to fit into + * @param {number} height - the height to fit into + */ + update: function(chart, width, height) { + if (!chart) { + return; + } + + var layoutOptions = chart.options.layout || {}; + var padding = helpers$1.options.toPadding(layoutOptions.padding); + + var availableWidth = width - padding.width; + var availableHeight = height - padding.height; + var boxes = buildLayoutBoxes(chart.boxes); + var verticalBoxes = boxes.vertical; + var horizontalBoxes = boxes.horizontal; + + // Essentially we now have any number of boxes on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays + // These locations are single-box locations only, when trying to register a chartArea location that is already taken, + // an error will be thrown. + // + // |----------------------------------------------------| + // | T1 (Full Width) | + // |----------------------------------------------------| + // | | | T2 | | + // | |----|-------------------------------------|----| + // | | | C1 | | C2 | | + // | | |----| |----| | + // | | | | | + // | L1 | L2 | ChartArea (C0) | R1 | + // | | | | | + // | | |----| |----| | + // | | | C3 | | C4 | | + // | |----|-------------------------------------|----| + // | | | B1 | | + // |----------------------------------------------------| + // | B2 (Full Width) | + // |----------------------------------------------------| + // + + var params = Object.freeze({ + outerWidth: width, + outerHeight: height, + padding: padding, + availableWidth: availableWidth, + vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length, + hBoxMaxHeight: availableHeight / 2 + }); + var chartArea = extend({ + maxPadding: extend({}, padding), + w: availableWidth, + h: availableHeight, + x: padding.left, + y: padding.top + }, padding); + + setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); + + // First fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + + // Then fit horizontal boxes + if (fitBoxes(horizontalBoxes, chartArea, params)) { + // if the area changed, re-fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + } + + handleMaxPadding(chartArea); + + // Finally place the boxes to correct coordinates + placeBoxes(boxes.leftAndTop, chartArea, params); + + // Move to opposite side of chart + chartArea.x += chartArea.w; + chartArea.y += chartArea.h; + + placeBoxes(boxes.rightAndBottom, chartArea, params); + + chart.chartArea = { + left: chartArea.left, + top: chartArea.top, + right: chartArea.left + chartArea.w, + bottom: chartArea.top + chartArea.h + }; + + // Finally update boxes in chartArea (radial scale for example) + helpers$1.each(boxes.chartArea, function(layout) { + var box = layout.box; + extend(box, chart.chartArea); + box.update(chartArea.w, chartArea.h); + }); + } +}; + +/** + * Platform fallback implementation (minimal). + * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 + */ + +var platform_basic = { + acquireContext: function(item) { + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + return item && item.getContext('2d') || null; + } +}; + +var platform_dom = "/*\n * DOM element rendering detection\n * https://davidwalsh.name/detect-node-insertion\n */\n@keyframes chartjs-render-animation {\n\tfrom { opacity: 0.99; }\n\tto { opacity: 1; }\n}\n\n.chartjs-render-monitor {\n\tanimation: chartjs-render-animation 0.001s;\n}\n\n/*\n * DOM element resizing detection\n * https://github.com/marcj/css-element-queries\n */\n.chartjs-size-monitor,\n.chartjs-size-monitor-expand,\n.chartjs-size-monitor-shrink {\n\tposition: absolute;\n\tdirection: ltr;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\toverflow: hidden;\n\tpointer-events: none;\n\tvisibility: hidden;\n\tz-index: -1;\n}\n\n.chartjs-size-monitor-expand > div {\n\tposition: absolute;\n\twidth: 1000000px;\n\theight: 1000000px;\n\tleft: 0;\n\ttop: 0;\n}\n\n.chartjs-size-monitor-shrink > div {\n\tposition: absolute;\n\twidth: 200%;\n\theight: 200%;\n\tleft: 0;\n\ttop: 0;\n}\n"; + +var platform_dom$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +'default': platform_dom +}); + +var stylesheet = getCjsExportFromNamespace(platform_dom$1); + +var EXPANDO_KEY = '$chartjs'; +var CSS_PREFIX = 'chartjs-'; +var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor'; +var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; +var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; +var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; + +/** + * DOM event types -> Chart.js event types. + * Note: only events with different types are mapped. + * @see https://developer.mozilla.org/en-US/docs/Web/Events + */ +var EVENT_TYPES = { + touchstart: 'mousedown', + touchmove: 'mousemove', + touchend: 'mouseup', + pointerenter: 'mouseenter', + pointerdown: 'mousedown', + pointermove: 'mousemove', + pointerup: 'mouseup', + pointerleave: 'mouseout', + pointerout: 'mouseout' +}; + +/** + * The "used" size is the final value of a dimension property after all calculations have + * been performed. This method uses the computed style of `element` but returns undefined + * if the computed style is not expressed in pixels. That can happen in some cases where + * `element` has a size relative to its parent and this last one is not yet displayed, + * for example because of `display: none` on a parent node. + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value + * @returns {number} Size in pixels or undefined if unknown. + */ +function readUsedSize(element, property) { + var value = helpers$1.getStyle(element, property); + var matches = value && value.match(/^(\d+)(\.\d+)?px$/); + return matches ? Number(matches[1]) : undefined; +} + +/** + * Initializes the canvas style and render size without modifying the canvas display size, + * since responsiveness is handled by the controller.resize() method. The config is used + * to determine the aspect ratio to apply in case no explicit height has been specified. + */ +function initCanvas(canvas, config) { + var style = canvas.style; + + // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it + // returns null or '' if no explicit value has been set to the canvas attribute. + var renderHeight = canvas.getAttribute('height'); + var renderWidth = canvas.getAttribute('width'); + + // Chart.js modifies some canvas values that we want to restore on destroy + canvas[EXPANDO_KEY] = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } + }; + + // Force canvas to display as block to avoid extra space caused by inline + // elements, which would interfere with the responsive resize process. + // https://github.com/chartjs/Chart.js/issues/2538 + style.display = style.display || 'block'; + + if (renderWidth === null || renderWidth === '') { + var displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } + + if (renderHeight === null || renderHeight === '') { + if (canvas.style.height === '') { + // If no explicit render height and style height, let's apply the aspect ratio, + // which one can be specified by the user but also by charts as default option + // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. + canvas.height = canvas.width / (config.options.aspectRatio || 2); + } else { + var displayHeight = readUsedSize(canvas, 'height'); + if (displayWidth !== undefined) { + canvas.height = displayHeight; + } + } + } + + return canvas; +} + +/** + * Detects support for options object argument in addEventListener. + * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support + * @private + */ +var supportsEventListenerOptions = (function() { + var supports = false; + try { + var options = Object.defineProperty({}, 'passive', { + // eslint-disable-next-line getter-return + get: function() { + supports = true; + } + }); + window.addEventListener('e', null, options); + } catch (e) { + // continue regardless of error + } + return supports; +}()); + +// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. +// https://github.com/chartjs/Chart.js/issues/4287 +var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; + +function addListener(node, type, listener) { + node.addEventListener(type, listener, eventListenerOptions); +} + +function removeListener(node, type, listener) { + node.removeEventListener(type, listener, eventListenerOptions); +} + +function createEvent(type, chart, x, y, nativeEvent) { + return { + type: type, + chart: chart, + native: nativeEvent || null, + x: x !== undefined ? x : null, + y: y !== undefined ? y : null, + }; +} + +function fromNativeEvent(event, chart) { + var type = EVENT_TYPES[event.type] || event.type; + var pos = helpers$1.getRelativePosition(event, chart); + return createEvent(type, chart, pos.x, pos.y, event); +} + +function throttled(fn, thisArg) { + var ticking = false; + var args = []; + + return function() { + args = Array.prototype.slice.call(arguments); + thisArg = thisArg || this; + + if (!ticking) { + ticking = true; + helpers$1.requestAnimFrame.call(window, function() { + ticking = false; + fn.apply(thisArg, args); + }); + } + }; +} + +function createDiv(cls) { + var el = document.createElement('div'); + el.className = cls || ''; + return el; +} + +// Implementation based on https://github.com/marcj/css-element-queries +function createResizer(handler) { + var maxSize = 1000000; + + // NOTE(SB) Don't use innerHTML because it could be considered unsafe. + // https://github.com/chartjs/Chart.js/issues/5902 + var resizer = createDiv(CSS_SIZE_MONITOR); + var expand = createDiv(CSS_SIZE_MONITOR + '-expand'); + var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink'); + + expand.appendChild(createDiv()); + shrink.appendChild(createDiv()); + + resizer.appendChild(expand); + resizer.appendChild(shrink); + resizer._reset = function() { + expand.scrollLeft = maxSize; + expand.scrollTop = maxSize; + shrink.scrollLeft = maxSize; + shrink.scrollTop = maxSize; + }; + + var onScroll = function() { + resizer._reset(); + handler(); + }; + + addListener(expand, 'scroll', onScroll.bind(expand, 'expand')); + addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); + + return resizer; +} + +// https://davidwalsh.name/detect-node-insertion +function watchForRender(node, handler) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + var proxy = expando.renderProxy = function(e) { + if (e.animationName === CSS_RENDER_ANIMATION) { + handler(); + } + }; + + helpers$1.each(ANIMATION_START_EVENTS, function(type) { + addListener(node, type, proxy); + }); + + // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class + // is removed then added back immediately (same animation frame?). Accessing the + // `offsetParent` property will force a reflow and re-evaluate the CSS animation. + // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics + // https://github.com/chartjs/Chart.js/issues/4737 + expando.reflow = !!node.offsetParent; + + node.classList.add(CSS_RENDER_MONITOR); +} + +function unwatchForRender(node) { + var expando = node[EXPANDO_KEY] || {}; + var proxy = expando.renderProxy; + + if (proxy) { + helpers$1.each(ANIMATION_START_EVENTS, function(type) { + removeListener(node, type, proxy); + }); + + delete expando.renderProxy; + } + + node.classList.remove(CSS_RENDER_MONITOR); +} + +function addResizeListener(node, listener, chart) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + + // Let's keep track of this added resizer and thus avoid DOM query when removing it. + var resizer = expando.resizer = createResizer(throttled(function() { + if (expando.resizer) { + var container = chart.options.maintainAspectRatio && node.parentNode; + var w = container ? container.clientWidth : 0; + listener(createEvent('resize', chart)); + if (container && container.clientWidth < w && chart.canvas) { + // If the container size shrank during chart resize, let's assume + // scrollbar appeared. So we resize again with the scrollbar visible - + // effectively making chart smaller and the scrollbar hidden again. + // Because we are inside `throttled`, and currently `ticking`, scroll + // events are ignored during this whole 2 resize process. + // If we assumed wrong and something else happened, we are resizing + // twice in a frame (potential performance issue) + listener(createEvent('resize', chart)); + } + } + })); + + // The resizer needs to be attached to the node parent, so we first need to be + // sure that `node` is attached to the DOM before injecting the resizer element. + watchForRender(node, function() { + if (expando.resizer) { + var container = node.parentNode; + if (container && container !== resizer.parentNode) { + container.insertBefore(resizer, container.firstChild); + } + + // The container size might have changed, let's reset the resizer state. + resizer._reset(); + } + }); +} + +function removeResizeListener(node) { + var expando = node[EXPANDO_KEY] || {}; + var resizer = expando.resizer; + + delete expando.resizer; + unwatchForRender(node); + + if (resizer && resizer.parentNode) { + resizer.parentNode.removeChild(resizer); + } +} + +/** + * Injects CSS styles inline if the styles are not already present. + * @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the