Commit 59985c6e authored by Mikel Johnson's avatar Mikel Johnson Committed by Nate Graham

Kickoff redesign

This commit completely overhauls Kickoff's user interface!

Original design (UI/UX) discussion
----------------------------------
- https://phabricator.kde.org/T12192
- !186

Features / issues
-----------------
New:
- Double pane UI
- Both keyboard and mouse navigation have been overhauled
- Power/Session actions are available in the footer
- Improved RTL support
- Grid view
- All applications view
- Accessibility

Screenshots / Videos
--------------------
Apps

![image](/uploads/b44c0e36d7687b22f9df563ed19aad19/image.png)

Places

![image](/uploads/93ad74bd8e037dc70d4927eb20a8ec29/image.png)

Search

![image](/uploads/f77da9df55bd1f9e75c0b0de96eebfd8/image.png)

Accessibility

https://youtu.be/Vqah3yL5h_E

Bugs fixed
----------
BUG: 357379

BUG: 369159

BUG: 369405

BUG: 378815

BUG: 387510

BUG: 387797

BUG: 391383

BUG: 392005

BUG: 393401

BUG: 394924

BUG: 398234

BUG: 398279

BUG: 398349

BUG: 398350

BUG: 398352

BUG: 398353

BUG: 405674

BUG: 408313

BUG: 410018

BUG: 412090

BUG: 415392

BUG: 416423

BUG: 419169

BUG: 423160

BUG: 425970

BUG: 426329

BUG: 427846

BUG: 428297

FIXED-IN: 5.21

@teams/vdg
parent 3a8aec38
One thing regarding kickoff is there probably shouldn't be any big changes before the release
Since the mockups were pretty strong is the first place and changing the UI would muddy the feedback (and we really want concrete feedback for this kind of change)
So basically:
- get the feedback from 5.21 release
- act on it instead trying to do something ourselves
Project Proposal: Kickoff KDE 4 Rewrite
This is a re-implementation of the Kickoff menu for KDE 3 originally created
by the OpenSuSE team.
Overall goals:
1. Implement the Kickoff user interface using Qt/KDE 4 technology
2. Make it easy for distributions to modify the menu contents
3. Make it easy for distributions to add their own branding
Detailed goals:
1. Implement the Kickoff user interface using Qt/KDE 4 technology
-> Clean separation of core and user interface
-> eg. Make good use of Qt 4's model/view classes
-> Use Strigi for search and analysis (or possibly just Xesam interface?)
-> Use Solid for getting data for removable device list
-> Attractive, original, but not overbearing graphical effects
using Qt 4's new painting and animation features.
To decide on:
-> Consult the Kickoff/KDE 3 developers and OpenSuSE users about
any desired changes to the user interface.
-> Whether to use Plasma applets and widgets or Qt's widgets and
QAbstractItemView as the base for the view classes.
-> Provision of Plasma data engines for applets to use to query
applications, recent files, favorites etc.
-> Should facilities be provided for distributions to replace
Strigi with their choice of search engine?
Ideas to explore in future:
-> Use Sonnet for spell-checking the user's search query
-> Use Phonon for previews of audio and video content
in the favorites and recently used sections
-> Light KWin integration for interesting effects for revealing,
hiding or rendering the Kickoff launcher on composited '3D'
desktops.
Goals 2 and 3 are derived from looking at the KDE 3 implementation
of Kickoff and also the way in which distributions customise KDE.
Consulting distribution representatives is important to clarify
these particular goals.
2. Make it easy for distributions to modify the menu contents
-> Different distributions may have different tools for system
configuration ( eg. System Settings , YaST , Mandriva's tools )
-> The available options on the leave page and the way in which
those tools work varies depending on the distribution
3. Make it easy for distributions to add their own branding
-> The launcher menu is a highly visible part of the desktop,
distributions are therefore likely to want to brand it with
their own logos, colors, icons etc.
This does not mean infinite theme-ability, but rather a good
out-of-the-box look which distros can easily tweak so that
it is obvious which distribution is being used from looking
at a screenshot, and also to allow distros to make some
obvious cosmetic changes between versions.
(See the Ubuntu art specs for example which state that each
new version is supposed to have a visual look which is
distinct from the previous version)
......@@ -18,12 +18,12 @@
import QtQuick 2.0
import org.kde.plasma.configuration 2.0
import org.kde.plasma.configuration 2.0 as PlasmaConfig
ConfigModel {
ConfigCategory {
name: i18n("General")
icon: "preferences-desktop-plasma"
source: "ConfigGeneral.qml"
PlasmaConfig.ConfigModel {
PlasmaConfig.ConfigCategory {
name: i18n("General")
icon: "preferences-desktop-plasma"
source: "ConfigGeneral.qml"
}
}
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name=""/>
<group name="General">
<entry name="switchTabsOnHover" type="Bool">
<label>Whether to switch between menu tabs by hovering them.</label>
<default>true</default>
</entry>
<entry name="showAppsByName" type="Bool">
<label>Whether to display specific application names instead of their generic names (e.g. Dolphin instead of File Manager).</label>
<default>true</default>
</entry>
<entry name="icon" type="String">
<label>The name of the icon used in the compact representation (e.g. on a small panel).</label>
<default>start-here-kde</default>
</entry>
<entry name="favorites" type="StringList">
<label>List of general favorites. Supported values are menu id's (usually .desktop file names), special URLs that expand into default applications (e.g. preferred://browser), document URLs and KPeople contact URIs.</label>
<default>preferred://browser,kontact.desktop,systemsettings.desktop,org.kde.dolphin.desktop,ktp-contactlist.desktop,org.kde.kate.desktop,org.kde.discover.desktop</default>
</entry>
<entry name="favoritesPortedToKAstats" type="Bool">
<label>Are the favorites ported to use KActivitiesStats to allow per-activity favorites</label>
<default>false</default>
</entry>
<entry name="systemApplications" type="StringList">
<label>List of applications at the top of the "Computer" tab.</label>
<default>systemsettings.desktop,org.kde.kinfocenter.desktop,org.kde.discover.desktop</default>
</entry>
<entry name="menuItems" type="StringList">
<label>The menu tabs to show.</label>
<default>bookmark:t,application:t,computer:t,used:t,oftenUsed:f,leave:t</default>
</entry>
<entry name="alphaSort" type="Bool">
<label>Whether to sort menu contents alphabetically or use manual/system sort order.</label>
<default>false</default>
</entry>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd">
<kcfgfile name=""/>
<group name="General">
<entry name="switchTabsOnHover" type="Bool">
<label>Whether to switch between menu tabs by hovering them.</label>
<default>true</default>
</entry>
<entry name="icon" type="String">
<label>The name of the icon used in the compact representation (e.g. on a small panel).</label>
<default>start-here-kde</default>
</entry>
<entry name="favorites" type="StringList">
<label>List of general favorites. Supported values are menu id's (usually .desktop file names), special URLs that expand into default applications (e.g. preferred://browser), document URLs and KPeople contact URIs.</label>
<default>preferred://browser,kontact.desktop,systemsettings.desktop,org.kde.dolphin.desktop,ktp-contactlist.desktop,org.kde.kate.desktop,org.kde.discover.desktop</default>
</entry>
<entry name="systemFavorites" type="StringList">
<label>List of system action favorites.</label>
<default>suspend,hibernate,reboot,shutdown</default>
</entry>
<entry name="primaryActions" type="Int">
<label>Which actions should be displayed in the footer: 0 = Power, 1 = Session, 2 = Custom</label>
<default>0</default>
</entry>
<entry name="favoritesPortedToKAstats" type="Bool">
<label>Are the favorites ported to use KActivitiesStats to allow per-activity favorites</label>
<default>false</default>
</entry>
<entry name="systemApplications" type="StringList">
<label>List of applications at the top of the "Computer" tab.</label>
<default>systemsettings.desktop,org.kde.kinfocenter.desktop,org.kde.discover.desktop</default>
</entry>
<entry name="favoritesDisplay" type="Int">
<label>How to display favorites: 0 = Grid, 1 = List</label>
<default>0</default>
</entry>
<entry name="gridAllowTwoLines" type="Bool">
<label>Whether to allow showing two lines in grid view.</label>
<default>true</default>
</entry>
<entry name="alphaSort" type="Bool">
<label>Whether to sort menu contents alphabetically or use manual/system sort order.</label>
<default>false</default>
</entry>
</group>
</kcfg>
/***************************************************************************
* Copyright (C) 2013 by Aurélien Gâteau <agateau@kde.org> *
* Copyright (C) 2014-2015 by Eike Hein <hein@kde.org> *
* Copyright (C) 2021 by Mikel Johnson <mikel5764@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
......@@ -20,27 +21,19 @@
import QtQuick 2.0
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.components 2.0 as PlasmaComponents // for Menu + MenuItem
Item {
id: root
id: actionMenuRoot
property QtObject menu
property Item visualParent
property variant actionList
property bool opened: menu ? (menu.status !== PlasmaComponents.DialogStatus.Closed) : false
signal actionClicked(string actionId, variant actionArgument)
signal closed
onActionListChanged: refreshMenu();
onOpenedChanged: {
if (!opened) {
closed();
}
}
function open(x, y) {
if (!actionList || !actionList.length) {
return;
......@@ -62,7 +55,7 @@ Item {
return;
}
menu = contextMenuComponent.createObject(root);
menu = contextMenuComponent.createObject(actionMenuRoot);
// actionList.forEach(function(actionItem) {
// var item = contextMenuItemComponent.createObject(menu, {
......@@ -78,7 +71,7 @@ Item {
if (actionItem.subActions) {
// This is a menu
var submenuItem = contextSubmenuItemComponent.createObject(
menu, { "actionItem" : actionItem });
menu, { "actionItem" : actionItem });
fillMenu(submenuItem.submenu, actionItem.subActions);
......@@ -97,8 +90,8 @@ Item {
Component {
id: contextMenuComponent
PlasmaComponents.ContextMenu {
visualParent: root.visualParent
PlasmaComponents.Menu {
visualParent: actionMenuRoot.visualParent
}
}
......@@ -115,7 +108,7 @@ Item {
property variant submenu : submenu_
PlasmaComponents.ContextMenu {
PlasmaComponents.Menu {
id: submenu_
visualParent: submenuItem.action
}
......@@ -137,7 +130,7 @@ Item {
checked : actionItem.checked ? actionItem.checked : false
onClicked: {
actionClicked(actionItem.actionId, actionItem.actionArgument);
actionMenuRoot.actionClicked(actionItem.actionId, actionItem.actionArgument);
}
}
}
......
/*
Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2012 Gregor Taetzner <gregor@freenet.de>
Copyright 2014 Sebastian Kügler <sebas@kde.org>
Copyright (C) 2015-2018 Eike Hein <hein@kde.org>
Copyright (C) 2021 by Mikel Johnson <mikel5764@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import QtQuick 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents3
FocusScope {
id: appViewContainer
property QtObject activatedSection: null
property string newBreadcrumbName: ""
signal appModelChange()
objectName: "ApplicationsGroupView"
property ListView listView: applicationsView.listView
function keyNavUp() {
return applicationsView.keyNavUp();
}
function keyNavDown() {
return applicationsView.keyNavDown();
}
function activateCurrentIndex() {
applicationsView.activatedItem = applicationsView.currentItem
applicationsView.moveRight()
}
function openContextMenu() {
applicationsView.currentItem.openActionMenu();
}
function reset() {
applicationsView.model = rootModel;
}
function refreshed() {
reset();
updatedLabelTimer.running = true;
}
Connections {
target: plasmoid
function onExpandedChanged() {
if (!plasmoid.expanded) {
reset();
}
}
}
KickoffListView {
id: applicationsView
isManagerMode: true
anchors.fill: parent
property Item activatedItem: null
property var newModel: null
Behavior on opacity {
NumberAnimation {
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
}
}
focus: true
appView: true
model: rootModel
function moveRight() {
var childModel = activatedItem.activate()
if (childModel != null) {
appViewContainer.activatedSection = childModel.model
appViewContainer.newBreadcrumbName = childModel.name
appViewContainer.appModelChange()
}
}
onReset: appViewContainer.reset()
}
// Displays text when application list gets updated
Timer {
id: updatedLabelTimer
// We want to have enough time to show that applications have been updated even for those who disabled animations
interval: 1500
running: false
repeat: true
onRunningChanged: {
if (running) {
updatedLabel.opacity = 1;
applicationsView.listView.opacity = 0.3;
}
}
onTriggered: {
updatedLabel.opacity = 0;
applicationsView.listView.opacity = 1;
running = false;
}
}
PlasmaComponents3.Label {
id: updatedLabel
text: i18n("Applications updated.")
opacity: 0
visible: opacity != 0
anchors.centerIn: parent
Behavior on opacity {
NumberAnimation {
duration: PlasmaCore.Units.shortDuration
easing.type: Easing.InOutQuad
}
}
}
Component.onCompleted: {
rootModel.cleared.connect(refreshed);
}
} // appViewContainer
......@@ -3,6 +3,7 @@
Copyright (C) 2012 Gregor Taetzner <gregor@freenet.de>
Copyright 2014 Sebastian Kügler <sebas@kde.org>
Copyright (C) 2015-2018 Eike Hein <hein@kde.org>
Copyright (C) 2021 by Mikel Johnson <mikel5764@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -20,32 +21,33 @@
*/
import QtQuick 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.components 3.0 as PlasmaComponents3
Item {
FocusScope {
id: appViewContainer
anchors.fill: parent
property QtObject activatedSection: null
property string rootBreadcrumbName: ""
signal appModelChange()
onAppModelChange: {
if (activatedSection != null) {
applicationsView.clearBreadcrumbs();
applicationsView.listView.model = activatedSection;
}
}
objectName: "ApplicationsView"
property ListView listView: applicationsView.listView
function decrementCurrentIndex() {
applicationsView.decrementCurrentIndex();
function keyNavUp() {
return applicationsView.keyNavUp();
}
function incrementCurrentIndex() {
applicationsView.incrementCurrentIndex();
function keyNavDown() {
return applicationsView.keyNavDown();
}
function activateCurrentIndex(start) {
if (!applicationsView.currentItem.modelChildren) {
if (!start) {
return;
}
}
function activateCurrentIndex() {
applicationsView.state = "OutgoingLeft";
}
......@@ -54,7 +56,7 @@ Item {
}
function deactivateCurrentIndex() {
if (crumbModel.count > 0) { // this is not the case when switching from the "Applications" to the "Favorites" tab using the "Left" key
if (crumbModel.count > 0) { // this is not the case when switching from the right sidebar to the left when going "left"
breadcrumbsElement.children[crumbModel.count-1].clickCrumb();
applicationsView.state = "OutgoingRight";
return true;
......@@ -63,8 +65,13 @@ Item {
}
function reset() {
applicationsView.model = rootModel;
applicationsView.model = activatedSection;
applicationsView.clearBreadcrumbs();
if (applicationsView.model == null) {
applicationsView.currentIndex = -1
} else {
applicationsView.currentIndex = 0
}
}
function refreshed() {
......@@ -80,7 +87,12 @@ Item {
}
}
}
Connections {
target: rootBreadcrumb
function onRootClick() {
applicationsView.newModel = activatedSection;
}
}
Item {
id: crumbContainer
......@@ -89,9 +101,42 @@ Item {
left: parent.left
right: parent.right
}
height: childrenRect.height
visible: applicationsView.model != null && applicationsView.model.description && applicationsView.model.description != "KICKER_ALL_MODEL"
height: visible ? breadcrumbFlickable.height : 0
Behavior on opacity {
NumberAnimation {
duration: PlasmaCore.Units.longDuration
easing.type: Easing.InOutQuad
}
}
PlasmaCore.SvgItem {
id: horizontalSeparator
opacity: applicationsView.listView.contentY !== 0
height: PlasmaCore.Units.devicePixelRatio
elementId: "horizontal-line"
z: 1
anchors {
left: parent.left
leftMargin: PlasmaCore.Units.smallSpacing * 4
right: parent.right
rightMargin: PlasmaCore.Units.smallSpacing * 4
bottom: parent.bottom
}
Behavior on opacity {
NumberAnimation {
duration: PlasmaCore.Units.shortDuration
easing.type: Easing.InOutQuad
}
}
Behavior on opacity { NumberAnimation { duration: PlasmaCore.Units.longDuration } }
svg: PlasmaCore.Svg {
imagePath: "widgets/line"
}
}
Flickable {
id: breadcrumbFlickable
......@@ -105,20 +150,19 @@ Item {
contentWidth: breadcrumbsElement.width
pixelAligned: true
//contentX: contentWidth - width
// HACK: Align the content to right for RTL locales
leftMargin: LayoutMirroring.enabled ? Math.max(0, width - contentWidth) : 0
PlasmaComponents.ButtonRow {
Row {
id: breadcrumbsElement
exclusive: false
spacing: PlasmaCore.Units.smallSpacing * 2
Breadcrumb {
id: rootBreadcrumb
root: true
text: i18n("All Applications")
text: rootBreadcrumbName
depth: 0
}
Repeater {
......@@ -146,12 +190,9 @@ Item {
KickoffListView {
id: applicationsView
anchors {
top: crumbContainer.bottom
bottom: parent.bottom
rightMargin: -PlasmaCore.Units.largeSpacing
leftMargin: -PlasmaCore.Units.largeSpacing
}
width: parent.width
......@@ -159,29 +200,40 @@ Item {
property Item activatedItem: null
property var newModel: null
Behavior on opacity { NumberAnimation { duration: PlasmaCore.Units.longDuration } }
section.property: model && model.description == "KICKER_ALL_MODEL" ? "display" : ""
section.criteria: ViewSection.FirstCharacter