Enhancement (#362)

1. 新增隐藏礼物选项
2. 无效技能ui显示🔒
3. 过期房间字符串显示删除线
5. 按钮键长按查看技能详情
6. 筛选房间功能
7. “禁用lua扩展”禁用
8. 调整服务器“从收藏移除”的ui,改为三点展开
9. 调整红温缩进
10. 房间内限制玩家名称长度(自己除外)
11. 玩家详情显示判定区
12. 房间内一览
13. 武将一览语音键增加按钮复制代码与文本(长按复制代码),悬停显示
14. 手牌排序多选:(默认)类型、点数、花色
15. 技能次数提示,指定为正数或0显示
16. 修复ArrangeCardsBox的报错
17. 手牌拖拽排序
18. 武将技能按顺序添加
This commit is contained in:
Nyutanislavsky 2024-09-18 23:53:38 +08:00 committed by GitHub
parent 3d942a24b0
commit e8aacf1888
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 922 additions and 104 deletions

View File

@ -56,11 +56,8 @@ Flickable {
skillDesc.append(ret)
}
data.skill.forEach(t => {
skillDesc.append("<b>" + luatr(t.name) + "</b>: " + t.description)
});
data.related_skill.forEach(t => {
skillDesc.append("<font color=\"purple\"><b>" + luatr(t.name) +
"</b>: " + t.description + "</font>")
skillDesc.append((t.is_related_skill ? "<font color=\"purple\"><b>" : "<b>") + luatr(t.name) +
"</b>: " + t.description + (t.is_related_skill ? "</font>" : ""));
});
skillDesc.append("\n");
});

View File

@ -212,5 +212,17 @@ Flickable {
skillDesc.append("--------------------");
skillDesc.append("<b>" + luatr(t.name) + "</b>: " + luatr(":" + t.name));
});
const judge = leval(
`(function()
local p = ClientInstance:getPlayerById(${id})
return p.player_cards[Player.Judge]
end)()`
);
judge.forEach(cid => {
const t = lcall("GetCardData", cid);
skillDesc.append("--------------------");
skillDesc.append("<b>" + luatr(t.name) + "</b>: " + luatr(":" + t.name));
});
}
}

View File

@ -40,7 +40,7 @@ ListView {
}
Button {
text: "Return to Bottom"
text: luatr("Return to Bottom")
visible: root.currentIndex !== logModel.count - 1
onClicked: root.currentIndex = logModel.count - 1;
}

View File

@ -20,12 +20,14 @@ QtObject {
property string preferedMode
property int preferedPlayerNum
property int preferredGeneralNum
property var preferredFilter
property string ladyImg
property real bgmVolume
property bool disableMsgAudio
property bool hideUseless
property bool hideObserverChatter
property bool rotateTableCard
property bool hidePresents
// property list<string> disabledGenerals: []
// property list<var> disableGeneralSchemes: []
// property int disableSchemeIdx: 0
@ -126,6 +128,13 @@ QtObject {
preferedMode = conf.preferedMode ?? "aaa_role_mode";
preferedPlayerNum = conf.preferedPlayerNum ?? 2;
preferredGeneralNum = conf.preferredGeneralNum ?? 3;
preferredFilter = conf.preferredFilter ?? {
name: "", //
id: "", // ID
modes : [], //
full : 2, // 012
hasPassword : 2, // 012
};
ladyImg = conf.ladyImg ?? AppPath + "/image/lady";
Backend.volume = conf.effectVolume ?? 50.;
bgmVolume = conf.bgmVolume ?? 50.;
@ -133,6 +142,7 @@ QtObject {
hideUseless = conf.hideUseless ?? false;
hideObserverChatter = conf.hideObserverChatter ?? false;
rotateTableCard = conf.rotateTableCard ?? false;
hidePresents = conf.hidePresents ?? false;
preferredTimeout = conf.preferredTimeout ?? 15;
preferredLuckTime = conf.preferredLuckTime ?? 0;
firstRun = conf.firstRun ?? true;
@ -165,6 +175,7 @@ QtObject {
// conf.disabledPack = disabledPack;
conf.preferedMode = preferedMode;
conf.preferedPlayerNum = preferedPlayerNum;
conf.preferredFilter = preferredFilter;
conf.ladyImg = ladyImg;
conf.preferredGeneralNum = preferredGeneralNum;
conf.effectVolume = Backend.volume;
@ -173,6 +184,7 @@ QtObject {
conf.hideUseless = hideUseless;
conf.hideObserverChatter = hideObserverChatter;
conf.rotateTableCard = rotateTableCard;
conf.hidePresents = hidePresents;
conf.preferredTimeout = preferredTimeout;
conf.preferredLuckTime = preferredLuckTime;
conf.firstRun = firstRun;

View File

@ -70,5 +70,13 @@ ColumnLayout {
}
}
Switch {
text: luatr("Hide presents")
checked: config.hidePresents
onCheckedChanged: {
config.hidePresents = checked;
}
}
}
}

View File

@ -0,0 +1,288 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Flickable {
id: root
height: parent.height
width: layout.width
anchors.fill: parent
anchors.margins: 16
clip: true
contentWidth: layout.width
contentHeight: layout.height
ScrollBar.vertical: ScrollBar {}
ScrollBar.horizontal: ScrollBar {} // considering long game mode name
signal finished()
ColumnLayout {
id: layout
anchors.top: parent.top
Item { Layout.fillHeight: true }
// roomId, roomName, gameMode, playerNum, capacity, hasPassword, outdated
GridLayout {
columns: 2
// roomName
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: luatr("Room Name")
font.bold: true
font.pixelSize: 14
}
TextField {
id: name
maximumLength: 64
font.pixelSize: 18
Layout.rightMargin: 16
Layout.fillWidth: true
text: config.preferredFilter.name
}
}
// roomId
RowLayout {
anchors.rightMargin: 8
spacing: 16
Text {
text: luatr("Room ID")
font.bold: true
font.pixelSize: 14
}
TextField {
id: id
maximumLength: 64
font.pixelSize: 18
Layout.rightMargin: 16
Layout.fillWidth: true
text: config.preferredFilter.id
}
}
}
// gameMode
ButtonGroup {
id: childModes
exclusive: false
checkState: parentModeBox.checkState
}
CheckBox {
id: parentModeBox
text: luatr("Game Mode")
font.bold: true
checkState: childModes.checkState
}
GridLayout {
columns: 6
Repeater {
id: modes
model: ListModel {
id: gameModeList
}
CheckBox {
text: name
checked: config.preferredFilter.modes.includes(name)
leftPadding: indicator.width
ButtonGroup.group: childModes
}
}
}
RowLayout {
anchors.rightMargin: 8
// spacing: 64
// Layout.fillWidth: true
// full
Column {
ButtonGroup {
id: childFull
exclusive: false
checkState: parentFullBox.checkState
}
CheckBox {
id: parentFullBox
text: luatr("Room Fullness")
font.bold: true
checkState: childFull.checkState
}
GridLayout {
columns: 6
Repeater {
id: fullStates
model: ["Full", "Not Full"]
CheckBox {
text: luatr(modelData)
checked: config.preferredFilter.full === index
leftPadding: indicator.width
ButtonGroup.group: childFull
}
}
}
}
// hasPassword
Column {
ButtonGroup {
id: childPw
exclusive: false
checkState: parentPwBox.checkState
}
CheckBox {
id: parentPwBox
text: luatr("Room Password")
font.bold: true
checkState: childPw.checkState
}
GridLayout {
columns: 6
Repeater {
id: pwStates
model: ["Has Password", "No Password"]
CheckBox {
text: luatr(modelData)
checked: config.preferredFilter.hasPassword === index
leftPadding: indicator.width
ButtonGroup.group: childPw
}
}
}
}
Button {
text: luatr("Clear")
onClicked: {
opTimer.start();
config.preferredFilter = {
name: "",
id: "",
modes : [],
full : 2,
hasPassword : 2,
}
config.preferredFilterChanged();
ClientInstance.notifyServer("RefreshRoomList", "");
lobby_dialog.item.finished();
}
}
Button {
text: luatr("Filter")
// width: 200
// enabled: !opTimer.running
onClicked: {
// opTimer.start();
filterRoom();
root.finished();
}
}
}
// capacity
/*
Column {
ButtonGroup {
id: childCapacity
exclusive: false
checkState: parentCapacityBox.checkState
}
CheckBox {
id: parentCapacityBox
text: luatr("Room Capacity")
font.bold: true
checkState: childCapacity.checkState
}
GridLayout {
columns: 6
Repeater {
id: capacityStates
model: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
CheckBox {
text: modelData
checked: false
leftPadding: indicator.width
ButtonGroup.group: childCapacity
}
}
}
}
*/
Component.onCompleted: {
const mode_data = lcall("GetGameModes");
mode_data.forEach(d => {
gameModeList.append(d);
});
}
}
function filterRoom() {
let f = config.preferredFilter;
f.name = name.text;
f.id = id.text;
// mode
let modeList = [];
if (parentModeBox.checkState === Qt.PartiallyChecked) {
for (let index = 0; index < modes.count; index++) {
var tCheckBox = modes.itemAt(index)
if (tCheckBox.checked) {modeList.push(tCheckBox.text)}
}
}
f.modes = modeList;
f.full = parentFullBox.checkState === Qt.PartiallyChecked ? (fullStates.itemAt(0).checked ? 0 : 1) : 2;
f.hasPassword = parentPwBox.checkState === Qt.PartiallyChecked ? (pwStates.itemAt(0).checked ? 0 : 1) : 2;
// capacity
/*
let capacityList = [];
if (parentCapacityBox.checkState === Qt.PartiallyChecked) {
for (let index = 0; index < capacityStates.count; index++) {
var nCheckBox = capacityStates.itemAt(index)
if (nCheckBox.checked) {capacityList.push(parseInt(nCheckBox.text))}
}
}
*/
config.preferredFilterChanged();
for (let i = roomModel.count - 1; i >= 0; i--) {
const r = roomModel.get(i);
if ((name.text !== '' && !r.roomName.includes(name.text))
|| (id.text !== '' && !r.roomId.toString().includes(id.text))
|| (modeList.length > 0 && !modeList.includes(luatr(r.gameMode)))
|| (f.full !== 2 &&
(f.full === 0 ? r.playerNum < r.capacity : r.playerNum >= r.capacity))
|| (f.hasPassword !== 2 &&
(f.hasPassword === 0 ? !r.hasPassword : r.hasPassword))
// || (capacityList.length > 0 && !capacityList.includes(r.capacity))
) {
roomModel.remove(i);
}
}
}
}

View File

@ -22,9 +22,11 @@ Flickable {
anchors.top: parent.top
anchors.topMargin: 8
/*
Switch {
text: luatr("Disable Extension")
}
*/
RowLayout {
Text {

View File

@ -1,8 +1,10 @@
module Fk.LobbyElement
AudioSetting 1.0 AudioSetting.qml
BanGeneralSetting 1.0 BanGeneralSetting.qml
BGSetting 1.0 BGSetting.qml
CreateRoom 1.0 CreateRoom.qml
EditProfile 1.0 EditProfile.qml
FilterRoom 1.0 FilterRoom.qml
PersonalSettings 1.0 PersonalSettings.qml
RoomGeneralSettings 1.0 RoomGeneralSettings.qml
RoomPackageSettings 1.0 RoomPackageSettings.qml

View File

@ -8,6 +8,7 @@ import Fk.RoomElement
Item {
id: root
objectName: "CardsOverview"
property bool loaded: false
@ -196,6 +197,7 @@ Item {
property var cards
function updateCard() {
const data = lcall("GetCardData", cid);
detailFlickable.contentY = 0; //
const suitTable = {
spade: "♠", heart: '<font color="red">♥</font>',
club: "♣", diamond: '<font color="red">♦</font>',
@ -234,6 +236,7 @@ Item {
}
Flickable {
id: detailFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: detailLayout.height
width: parent.width - 40
@ -323,6 +326,7 @@ Item {
Button {
text: luatr("Quit")
anchors.right: parent.right
visible: mainStack.currentItem.objectName === "CardsOverview"
onClicked: {
mainStack.pop();
}

View File

@ -9,6 +9,7 @@ import "RoomLogic.js" as RoomLogic
Item {
id: root
objectName: "GeneralsOverview"
property bool loaded: false
property int stat: 0 // 0=normal 1=banPkg 2=banChara
@ -165,6 +166,7 @@ Item {
return luatr("BanGeneral");
}
enabled: stat !== 1
visible: mainStack.currentItem.objectName === "GeneralsOverview"
onClicked: {
if (stat === 0) {
stat = 2;
@ -182,6 +184,7 @@ Item {
return luatr("BanPackage");
}
enabled: stat !== 2
visible: mainStack.currentItem.objectName === "GeneralsOverview"
onClicked: {
if (stat === 0) {
stat = 1;
@ -194,6 +197,7 @@ Item {
ToolButton {
text: luatr("Quit")
font.pixelSize: 20
visible: mainStack.currentItem.objectName === "GeneralsOverview"
onClicked: {
mainStack.pop();
config.saveConf();
@ -283,9 +287,9 @@ Item {
const gdata = lcall("GetGeneralData", modelData);
const pack = gdata.package;
if (s.banPkg[pack]) {
if (s.banPkg[pack].includes(modelData)) return '启用';
if (s.banPkg[pack].includes(modelData)) return luatr('Enable');
} else {
if (!!s.normalPkg[pack]?.includes(modelData)) return '禁';
if (!!s.normalPkg[pack]?.includes(modelData)) return luatr('Prohibit');
}
}
anchors.centerIn: parent
@ -370,17 +374,9 @@ Item {
Text {
Layout.fillWidth: true
text: {
const orig = '$' + name + (idx ? idx.toString() : "");
const orig_trans = luatr(orig);
// try general specific
const orig_g = '$' + name + '_' + detailGeneralCard.name
const orig = '$' + name + (specific ? '_' + detailGeneralCard.name : "")
+ (idx ? idx.toString() : "");
const orig_g_trans = luatr(orig_g);
if (orig_g_trans !== orig_g) {
return orig_g_trans;
}
const orig_trans = luatr(orig);
if (orig_trans !== orig) {
return orig_trans;
@ -396,10 +392,51 @@ Item {
callbacks["LogEvent"]({
type: "PlaySkillSound",
name: name,
general: detailGeneralCard.name,
general: specific ? detailGeneralCard.name : null, //
i: idx,
});
}
onPressAndHold: {
Backend.copyToClipboard('$' + name + (specific ? '_' + detailGeneralCard.name : "")
+ (idx ? idx.toString() : "") + ':');
toast.show(luatr("Audio Code Copy Success"));
}
ToolButton {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Layout.preferredWidth: 32
Layout.preferredHeight: 32
visible: parent.hovered
text: "⋮"
onClicked: {
if (skillAudioMenu.visible){
skillAudioMenu.close();
} else {
skillAudioMenu.open();
}
}
Menu {
id: skillAudioMenu
MenuItem {
text: luatr("Copy Audio Code")
onTriggered: {
Backend.copyToClipboard('$' + name + (specific ? '_' + detailGeneralCard.name : "")
+ (idx ? idx.toString() : "") + ':');
toast.show(luatr("Audio Code Copy Success"));
}
}
MenuItem {
text: luatr("Copy Audio Text")
onTriggered: {
Backend.copyToClipboard(luatr('$' + name + (specific ? '_' + detailGeneralCard.name : "")
+ (idx ? idx.toString() : "")));
toast.show(luatr("Audio Text Copy Success"));
}
}
}
}
}
}
@ -427,7 +464,7 @@ Item {
if (Backend.exists(fname)) {
ret = true;
audioModel.append({ name: skill, idx: i });
audioModel.append({ name: skill, idx: i, specific: true });
} else {
if (i > 0) break;
}
@ -445,7 +482,7 @@ Item {
skill + (i !== 0 ? i.toString() : "") + ".mp3";
if (Backend.exists(fname)) {
audioModel.append({ name: skill, idx: i });
audioModel.append({ name: skill, idx: i, specific: false});
} else {
if (i > 0) break;
}
@ -465,6 +502,7 @@ Item {
function updateGeneral() {
detailGeneralCard.name = general;
detailFlickable.contentY = 0; //
const data = lcall("GetGeneralDetail", general);
generalText.clear();
audioModel.clear();
@ -479,14 +517,8 @@ Item {
}
data.skill.forEach(t => {
generalText.append("<b>" + luatr(t.name) +
"</b>: " + t.description);
addSkillAudio(t.name);
});
data.related_skill.forEach(t => {
generalText.append("<font color=\"purple\"><b>" + luatr(t.name) +
"</b>: " + t.description + "</font>");
generalText.append((t.is_related_skill ? "<font color=\"purple\"><b>" : "<b>") + luatr(t.name) +
"</b>: " + t.description + (t.is_related_skill ? "</font>" : ""));
addSkillAudio(t.name);
});
@ -521,6 +553,8 @@ Item {
wrapMode: Text.WordWrap
textFormat: TextEdit.RichText
font.pixelSize: 16
lineHeight: 21
lineHeightMode: Text.FixedHeight
function trans(str) {
const ret = luatr(str);
if (ret === str) {
@ -530,13 +564,18 @@ Item {
}
text: {
const general = generalDetail.general;
return [
luatr(lcall("GetGeneralData", general).package),
const gdata = lcall("GetGeneralData", general);
let ret = [
luatr(gdata.package),
luatr("Title") + trans("#" + general),
luatr("Designer") + trans("designer:" + general),
luatr("Voice Actor") + trans("cv:" + general),
luatr("Illustrator") + trans("illustrator:" + general),
].join("<br>");
if (gdata.hidden) {
ret += "<br><font color=\"grey\">" + luatr("Hidden General") + "</font>";
}
return ret;
}
}
@ -547,6 +586,7 @@ Item {
Button {
text: luatr("Set as Avatar")
visible: mainStack.currentItem.objectName === "GeneralsOverview"
enabled: detailGeneralCard.name !== "" && !opTimer.running
&& Self.avatar !== detailGeneralCard.name
onClicked: {
@ -562,6 +602,7 @@ Item {
}
Flickable {
id: detailFlickable
flickableDirection: Flickable.VerticalFlick
contentHeight: detailLayout.height
width: parent.width - 40 - generalInfo.width
@ -628,6 +669,44 @@ Item {
Backend.playSound("./packages/" + extension + "/audio/death/"
+ general);
}
onPressAndHold: {
Backend.copyToClipboard("$~" + generalDetail.general);
toast.show(luatr("Audio Code Copy Success"));
}
ToolButton {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
Layout.preferredWidth: 32
Layout.preferredHeight: 32
visible: parent.hovered
text: "⋮"
onClicked: {
if (deathAudioMenu.visible){
deathAudioMenu.close();
} else {
deathAudioMenu.open();
}
}
Menu {
id: deathAudioMenu
MenuItem {
text: luatr("Copy Audio Code")
onTriggered: {
Backend.copyToClipboard("$~" + generalDetail.general);
toast.show(luatr("Audio Code Copy Success"));
}
}
MenuItem {
text: luatr("Copy Audio Text")
onTriggered: {
Backend.copyToClipboard(luatr("~" + generalDetail.general));
toast.show(luatr("Audio Text Copy Success"));
}
}
}
}
}
}
}

View File

@ -102,6 +102,32 @@ Item {
}
}
ToolButton {
x: parent.width - 32
y: parent.height / 2 - 8
Layout.preferredWidth: 32
Layout.preferredHeight: 32
visible: !!favorite
text: "⋮"
onClicked: {
if (menu.visible){
menu.close();
} else {
menu.open();
}
}
Menu {
id: menu
MenuItem {
text: qsTr("Remove from Favorites")
onTriggered: {
removeFavorite(addr, port);
}
}
}
}
ColumnLayout {
x: 6
height: parent.height
@ -125,7 +151,7 @@ Item {
height: childrenRect.height
width: serverList.width
Text {
text: "已收藏服务器与公共服务器列表"
text: qsTr("List of Favorites and Public Servers")
font.pixelSize: 18
x: 32; y: 8
}
@ -170,14 +196,14 @@ Item {
id: addressEdit
maximumLength: 64
Layout.fillWidth: true
placeholderText: "服务器地址"
placeholderText: qsTr("Server Address")
text: selectedServer?.addr ?? ""
}
TextField {
id: portEdit
maximumLength: 6
Layout.fillWidth: true
placeholderText: "端口"
placeholderText: qsTr("Port")
text: selectedServer?.port ?? ""
}
Flickable {
@ -199,21 +225,21 @@ Item {
id: usernameEdit
maximumLength: 32
Layout.fillWidth: true
placeholderText: "用户名"
placeholderText: qsTr("Username")
text: selectedServer?.username ?? ""
}
TextField {
id: passwordEdit
maximumLength: 32
Layout.fillWidth: true
placeholderText: "密码"
placeholderText: qsTr("Password")
passwordCharacter: "*"
echoMode: TextInput.Password
text: selectedServer?.password ?? ""
}
}
Button {
text: "登录(首次登录自动注册)"
text: qsTr("LOGIN (Auto-registration)")
Layout.fillWidth: true
enabled: !!(addressEdit.text && portEdit.text &&
usernameEdit.text && passwordEdit.text)
@ -237,9 +263,9 @@ Item {
}
}
Button {
text: "从收藏夹删除"
text: qsTr("Remove from Favorites")
Layout.fillWidth: true
visible: !!(selectedServer?.favorite)
visible: false // !!(selectedServer?.favorite) //
onClicked: {
removeFavorite(selectedServer.addr, selectedServer.port);
}

View File

@ -35,6 +35,7 @@ Item {
text: roomName
// color: outdated ? "gray" : "black"
font.pixelSize: 16
font.strikeout: outdated
// elide: Label.ElideRight
anchors.top: parent.top
anchors.left: parent.left
@ -44,6 +45,7 @@ Item {
Text {
id: roomIdText
text: luatr(gameMode) + ' #' + roomId
font.strikeout: outdated
anchors.top: roomNameText.bottom
anchors.left: roomNameText.left
}
@ -119,7 +121,7 @@ Item {
text: "在未来的版本中这一块区域将增加更多实用的功能,<br>"+
"例如直接查看房间的各种配置信息<br>"+
"以及更多与禁将有关的实用功能!"+
"<font color='gray'>注:绿色按钮为试做型UI 后面优化</font>"
"<font color='gray'>注:色按钮为试做型UI 后面优化</font>"
font.pixelSize: 18
}
@ -195,6 +197,13 @@ Item {
ClientInstance.notifyServer("RefreshRoomList", "");
}
}
Button {
text: luatr("Filter")
onClicked: {
lobby_dialog.sourceComponent = Qt.createComponent("../LobbyElement/FilterRoom.qml"); //roomFilterDialog;
lobby_drawer.open();
}
}
Button {
text: luatr("Create Room")
onClicked: {
@ -313,7 +322,7 @@ Item {
}
}
Button {
text: luatr("Scenarios Overview")
text: luatr("Modes Overview")
onClicked: {
mainStack.push(mainWindow.modesOverviewPage);
}

View File

@ -13,6 +13,7 @@ Item {
property int padding: 5
signal clicked
signal rightClicked
id: button
width: icon.width + title.implicitWidth + padding * 2
@ -44,7 +45,19 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton
gesturePolicy: TapHandler.WithinBounds
onTapped: if (parent.enabled) parent.clicked()
onTapped: (p, btn) => {
if (parent.enabled) {
if (btn === Qt.LeftButton || btn === Qt.NoButton) {
parent.clicked();
} else if (btn === Qt.RightButton) {
parent.rightClicked();
}
}
}
onLongPressed: {
parent.rightClicked();
}
}
HoverHandler {

View File

@ -5,6 +5,7 @@ import QtQuick.Layouts
import QtQuick.Controls
Item {
objectName: "ModesOverview"
RowLayout {
anchors.fill: parent
spacing: 10
@ -30,6 +31,7 @@ Item {
TapHandler {
onTapped: {
listView.currentIndex = index;
detailFlickable.contentY = 0; //
}
}
}
@ -40,6 +42,7 @@ Item {
Layout.fillHeight: true
color: "#88EEEEEE"
Flickable {
id: detailFlickable
width: parent.width - 16
height: parent.height - 16
anchors.centerIn: parent
@ -62,6 +65,7 @@ Item {
Button {
text: luatr("Quit")
anchors.bottom: parent.bottom
visible: mainStack.currentItem.objectName === "ModesOverview"
onClicked: {
mainStack.pop();
}
@ -72,5 +76,6 @@ Item {
for (let d of mode_data) {
modeList.append(d);
}
listView.currentIndex = 0;
}
}

View File

@ -100,11 +100,12 @@ Item {
Menu {
id: menuContainer
y: menuButton.height - 12
width: 100
width: parent.width * 1.8
MenuItem {
id: quitButton
text: luatr("Quit")
icon.source: AppPath + "/image/modmaker/back"
onClicked: {
if (config.replaying) {
Backend.controlReplayer("shutdown");
@ -117,10 +118,57 @@ Item {
}
}
MenuItem {
id: volumeButton
text: luatr("Audio Settings")
icon.source: AppPath + "/image/button/tileicon/configure"
onClicked: {
volumeDialog.open();
}
}
Menu {
title: luatr("Overview")
icon.source: AppPath + "/image/button/tileicon/rule_summary"
icon.width: 24
icon.height: 24
icon.color: palette.windowText
MenuItem {
id: generalButton
text: luatr("Generals Overview")
icon.source: AppPath + "/image/button/tileicon/general_overview"
onClicked: {
overviewLoader.overviewType = "Generals";
overviewDialog.open();
overviewLoader.item.loadPackages();
}
}
MenuItem {
id: cardslButton
text: luatr("Cards Overview")
icon.source: AppPath + "/image/button/tileicon/card_overview"
onClicked: {
overviewLoader.overviewType = "Cards";
overviewDialog.open();
overviewLoader.item.loadPackages();
}
}
MenuItem {
id: modesButton
text: luatr("Modes Overview")
icon.source: AppPath + "/image/misc/paper"
onClicked: {
overviewLoader.overviewType = "Modes";
overviewDialog.open();
}
}
}
MenuItem {
id: surrenderButton
enabled: !config.observing && !config.replaying
enabled: !config.observing && !config.replaying && isStarted
text: luatr("Surrender")
icon.source: AppPath + "/image/misc/surrender"
onClicked: {
const photo = getPhoto(Self.id);
if (isStarted && !(photo.dead && photo.rest <= 0)) {
@ -130,21 +178,13 @@ Item {
luatr('Surrender is disabled in this mode');
} else {
surrenderDialog.informativeText = surrenderCheck
.map(str => `${luatr(str.text)}${str.passed ? '√' : '×'}`)
.map(str => `${luatr(str.text)}${str.passed ? '✓' : '✗'}`)
.join('<br>');
}
surrenderDialog.open();
}
}
}
MenuItem {
id: volumeButton
text: luatr("Audio Settings")
onClicked: {
volumeDialog.open();
}
}
}
}
@ -468,7 +508,68 @@ Item {
MetroButton {
text: luatr("Sort Cards")
textFont.pixelSize: 28
onClicked: Logic.resortHandcards();
onClicked: {
if (lcall("CanSortHandcards", Self.id)) {
let sortMethods = [];
for (let index = 0; index < sortMenuRepeater.count; index++) {
var tCheckBox = sortMenuRepeater.itemAt(index)
sortMethods.push(tCheckBox.checked)
}
Logic.sortHandcards(sortMethods);
}
}
onRightClicked: {
if (sortMenu.visible) {
sortMenu.close();
} else {
sortMenu.open();
}
}
Menu {
id: sortMenu
x: parent.width
y: -25
width: parent.width * 2
background: Rectangle {
color: "black"
border.width: 3
border.color: "white"
opacity: 0.8
}
Repeater {
id: sortMenuRepeater
model: ["Sort by Type", "Sort by Number", "Sort by Suit"]
CheckBox {
id: control
text: "<font color='white'>" + luatr(modelData) + "</font>"
checked: modelData === "Sort by Type"
font.pixelSize: 20
indicator: Rectangle {
implicitWidth: 26
implicitHeight: 26
x: control.leftPadding
y: control.height / 2 - height / 2
radius: 3
border.color: "white"
Rectangle {
width: 14
height: 14
x: 6
y: 6
radius: 2
color: control.down ? "#17a81a" : "#21be2b"
visible: control.checked
}
}
}
}
}
}
MetroButton {
text: luatr("Chat")
@ -994,6 +1095,28 @@ Item {
}
}
Popup {
id: overviewDialog
width: realMainWin.width * 0.75
height: realMainWin.height * 0.75
anchors.centerIn: parent
background: Rectangle {
color: "#EEEEEEEE"
radius: 5
border.color: "#A6967A"
border.width: 1
}
Loader {
id: overviewLoader
property string overviewType: "Generals"
anchors.centerIn: parent
width: parent.width / mainWindow.scale
height: parent.height / mainWindow.scale
scale: mainWindow.scale
source: AppPath + "/Fk/Pages/" + overviewType + "Overview.qml"
}
}
GlowText {
anchors.centerIn: dashboard
visible: Logic.getPhoto(Self.id).rest > 0 && !config.observing
@ -1127,6 +1250,8 @@ Item {
const general = luatr(data.general);
if (msg.startsWith("!")) {
if (config.hidePresents)
return true;
const splited = msg.split(":");
const type = splited[0].slice(1);
switch (type) {

View File

@ -297,13 +297,27 @@ function moveCards(moves) {
}
}
const suitInteger = {
spade: 1, heart: 3,
club: 2, diamond: 4,
}
function resortHandcards() {
function sortHandcards(sortMethods) {
if (!dashboard.handcardArea.cards.length) {
return;
}
const cardType = sortMethods[0];
const cardNum = sortMethods[1];
const cardSuit = sortMethods[2];
if (!cardType && !cardNum && !cardSuit) {
return;
}
let sortOutputs = [];
let sortedStatus = [];
const subtypeString2Number = {
["none"]: Card.SubtypeNone,
["delayed_trick"]: Card.SubtypeDelayedTrick,
@ -318,51 +332,61 @@ function resortHandcards() {
return c.cid;
})
dashboard.handcardArea.cards.sort((prev, next) => {
if (prev.footnote === next.footnote) {
if (prev.type === next.type) {
const prevSubtypeNumber = subtypeString2Number[prev.subtype];
const nextSubtypeNumber = subtypeString2Number[next.subtype];
if (prevSubtypeNumber === nextSubtypeNumber) {
const splitedPrevName = prev.name.split('__');
const prevTrueName = splitedPrevName[splitedPrevName.length - 1];
let sortedByType = true;
let handcards
if (cardType) {
handcards = dashboard.handcardArea.cards.slice(0);
handcards.sort((prev, next) => {
if (prev.footnote === next.footnote) {
if (prev.type === next.type) {
const prevSubtypeNumber = subtypeString2Number[prev.subtype];
const nextSubtypeNumber = subtypeString2Number[next.subtype];
if (prevSubtypeNumber === nextSubtypeNumber) {
const splitedPrevName = prev.name.split('__');
const prevTrueName = splitedPrevName[splitedPrevName.length - 1];
const splitedNextName = next.name.split('__');
const nextTrueName = splitedNextName[splitedNextName.length - 1];
if (prevTrueName === nextTrueName) {
return prev.cid - next.cid;
const splitedNextName = next.name.split('__');
const nextTrueName = splitedNextName[splitedNextName.length - 1];
if (prevTrueName === nextTrueName) {
return prev.cid - next.cid;
} else {
return prevTrueName > nextTrueName ? -1 : 1;
}
} else {
return prevTrueName > nextTrueName ? -1 : 1;
return prevSubtypeNumber - nextSubtypeNumber;
}
} else {
return prevSubtypeNumber - nextSubtypeNumber;
return prev.type - next.type;
}
} else {
return prev.type - next.type;
return prev.footnote > next.footnote ? 1 : -1;
}
} else {
return prev.footnote > next.footnote ? 1 : -1;
}
});
});
let i = 0;
let resort = true;
dashboard.handcardArea.cards.forEach(c => {
if (hand[i] !== c.cid) {
resort = false;
return;
}
i++;
})
// Check if the cards are sorted by type
let i = 0;
handcards.every(c => {
if (hand[i] !== c.cid) {
sortedByType = false;
return false;
}
i++;
return true;
})
sortOutputs.push(handcards);
sortedStatus.push(sortedByType);
}
if (resort) {
dashboard.handcardArea.cards.sort((prev, next) => {
let sortedByNum = true;
if (cardNum) {
handcards = dashboard.handcardArea.cards.slice(0);
handcards.sort((prev, next) => {
if (prev.footnote === next.footnote) {
if (prev.number === next.number) { // 按点数排
if (prev.suit === next.suit) {
if (prev.number === next.number) {
if (suitInteger[prev.suit] === suitInteger[next.suit]) {
return prev.cid - next.cid;
} else {
return prev.suit - next.suit;
return suitInteger[prev.suit] - suitInteger[next.suit];
}
} else {
return prev.number - next.number;
@ -371,8 +395,61 @@ function resortHandcards() {
return prev.footnote > next.footnote ? 1 : -1;
}
});
let i = 0;
handcards.every(c => {
if (hand[i] !== c.cid) {
sortedByNum = false;
return false;
}
i++;
return true;
})
sortOutputs.push(handcards);
sortedStatus.push(sortedByNum);
}
let sortedBySuit = true;
if (cardSuit) {
handcards = dashboard.handcardArea.cards.slice(0);
handcards.sort((prev, next) => {
if (prev.footnote === next.footnote) {
if (suitInteger[prev.suit] === suitInteger[next.suit]) {
if (prev.number === next.number) {
return prev.cid - next.cid;
} else {
return prev.number - next.number;
}
} else {
return suitInteger[prev.suit] - suitInteger[next.suit];
}
} else {
return prev.footnote > next.footnote ? 1 : -1;
}
});
let i = 0;
handcards.every(c => {
if (hand[i] !== c.cid) {
sortedBySuit = false;
return false;
}
i++;
return true;
})
sortOutputs.push(handcards);
sortedStatus.push(sortedBySuit);
}
let output
for (let i = 0; i < sortedStatus.length; i++) {
if (sortedStatus[i]) {
let j = i < sortedStatus.length - 1 ? i + 1 : 0;
output = sortOutputs[j];
break;
}
}
if (!output) output = sortOutputs[0];
dashboard.handcardArea.cards = output;
dashboard.handcardArea.updateCardPosition(true);
}
@ -933,6 +1010,18 @@ callbacks["UpdateCard"] = (j) => {
card.setData(lcall("GetCardData", id));
}
callbacks["UpdateSkill"] = (j) => {
const all_skills = [roomScene.dashboard.skillButtons, roomScene.dashboard.notActiveButtons];
for (const skills of all_skills) {
for (let i = 0; i < skills.count; i++) {
const item = skills.itemAt(i);
const dat = lcall("GetSkillStatus", item.orig);
item.locked = dat.locked;
item.times = dat.times;
}
}
}
callbacks["StartGame"] = (jsonData) => {
roomScene.isStarted = true;

View File

@ -82,8 +82,7 @@ GraphicsBox {
}
Row {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
Layout.alignment: Qt.AlignHCenter
spacing: 32
MetroButton {

View File

@ -69,7 +69,7 @@ Item {
signal rightClicked()
signal doubleClicked()
signal thrown()
signal released()
signal released(var card)
signal entered()
signal exited()
signal moveFinished()
@ -287,7 +287,7 @@ Item {
onGrabChanged: (transtition, point) => {
if (transtition !== PointerDevice.UngrabExclusive) return;
parent.released();
parent.released(root);
if (autoBack)
goBackAnimation.start();
}

View File

@ -17,6 +17,7 @@ RowLayout {
property int selected_card: -1
property alias skillButtons: skillPanel.skill_buttons
property alias notActiveButtons: skillPanel.not_active_buttons
property var expanded_piles: ({}) // name -> int[]
property var extra_cards: []

View File

@ -13,6 +13,7 @@ Item {
property alias style: textItem.style
property alias styleColor: textItem.styleColor
property alias wrapMode: textItem.wrapMode
property alias elide: textItem.elide
property alias lineHeight: textItem.lineHeight
property alias glow: glowItem

View File

@ -7,6 +7,7 @@ Item {
property alias cards: cardArea.cards
property alias length: cardArea.length
property var selectedCards: []
property var movepos
signal cardSelected(int cardId, bool selected)
@ -32,9 +33,11 @@ Item {
function filterInputCard(card)
{
card.autoBack = true;
card.draggable = true;
card.draggable = lcall("CanSortHandcards", Self.id);
card.selectable = false;
card.clicked.connect(adjustCards);
card.released.connect(updateCardReleased);
card.xChanged.connect(updateCardDragging);
}
function remove(outputs)
@ -46,6 +49,8 @@ Item {
card.draggable = false;
card.selectable = false;
card.selectedChanged.disconnect(adjustCards);
card.released.disconnect(updateCardReleased);
card.xChanged.disconnect(updateCardDragging);
card.prohibitReason = "";
}
return result;
@ -84,6 +89,49 @@ Item {
}
}
function updateCardDragging()
{
let _card, c;
let index;
for (index = 0; index < cards.length; index++) {
c = cards[index];
if (c.dragging) {
_card = c;
break;
}
}
if (!_card) return;
_card.goBackAnim.stop();
_card.opacity = 0.8
let card;
movepos = null;
for (let i = 0; i < cards.length; i++) {
card = cards[i];
if (card.dragging) continue;
if (card.x > _card.x) {
movepos = i - (index < i ? 1 : 0);
break;
}
}
if (movepos == null) { //
movepos = cards.length;
}
}
function updateCardReleased(_card)
{
let i;
if (movepos != null) {
i = cards.indexOf(_card);
cards.splice(i, 1);
cards.splice(movepos, 0, _card);
movepos = null;
}
updateCardPosition(true);
}
function adjustCards()
{
area.updateCardPosition(true);

View File

@ -566,6 +566,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 2
width: parent.width - role.width - hp.width - 20
font.pixelSize: 16
text: {
@ -574,7 +575,8 @@ Item {
ret = luatr("<Blocked> ") + ret;
return ret;
}
elide: root.playerid === Self.id ? Text.ElideNone : Text.ElideMiddle
horizontalAlignment: Qt.AlignHCenter
glow.radius: 8
}

View File

@ -7,6 +7,7 @@ Flickable {
id: root
property alias skill_buttons: skill_buttons
property alias prelight_buttons: prelight_buttons
property alias not_active_buttons: not_active_buttons
clip: true
contentWidth: panel.width
@ -91,6 +92,7 @@ Flickable {
columnSpacing: 2
rowSpacing: 2
Repeater {
id: not_active_buttons
model: not_active_skills
onItemAdded: parent.forceLayout()
SkillButton {

View File

@ -11,6 +11,8 @@ Item {
property string orig: ""
property bool pressed: false
property bool prelighted: false
property bool locked: false
property int times: -1
onEnabledChanged: {
if (!enabled)
@ -86,6 +88,76 @@ Item {
}
}
Image {
source: AppPath + "/image/button/skill/locked.png"
scale: 0.8
z: 2
visible: root.locked
opacity: 0.8
anchors.centerIn: parent
}
Item {
width: 12
height: 12
visible: root.times > -1
anchors.right: parent.right
anchors.rightMargin: type === "notactive" ? -13 : 5
anchors.top: parent.top
anchors.topMargin: 5
Rectangle {
width: Math.max(15, 1.4 * count.contentWidth)
height: 15
radius: width * 0.5
x: (parent.width - width) / 2
y: -1.5
color: "transparent"
border.color: "#D2AD4A"
border.width: 1.1
}
Text {
id: count
anchors.centerIn: parent
font.pixelSize: 16
font.family: fontLibian.name
font.bold: true
text: root.times
z: 1.5
}
Glow {
source: count
anchors.fill: count
color: "black"
spread: 0.3
radius: 5
}
LinearGradient {
anchors.fill: count
z: 3
source: count
gradient: Gradient {
GradientStop {
position: 0
color: "#FEF7C2"
}
GradientStop {
position: 0.8
color: "#D2AD4A"
}
GradientStop {
position: 1
color: "#BE9878"
}
}
}
}
TapHandler {
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.NoButton
onTapped: (p, btn) => {
@ -95,6 +167,10 @@ Item {
skillDetail.open();
}
}
onLongPressed: {
skillDetail.open();
}
}
Popup {

View File

@ -105,10 +105,10 @@ Item {
for (i = 0; i < outputs.length; i++) {
let exists = false;
for (j = 0; j < result.length; j++) {
if (result[j].cid === outputs[i]) {
exists = true;
break;
}
if (result[j].cid === outputs[i]) {
exists = true;
break;
}
}
if (!exists)
vanished.push(outputs[i]);

BIN
image/misc/paper.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

BIN
image/misc/surrender.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -1 +0,0 @@
Subproject commit 4fd2070d099d1f967d1070d72beb0fae2cb6e4be

View File

@ -227,6 +227,10 @@
<source>Edit Server</source>
<translation></translation>
</message>
<message>
<source>List of Favorites and Public Servers</source>
<translation></translation>
</message>
<message>
<source>Refresh List</source>
<translation></translation>
@ -239,6 +243,14 @@
<source>Go Back</source>
<translation></translation>
</message>
<message>
<source>Server Address</source>
<translation></translation>
</message>
<message>
<source>Port</source>
<translation></translation>
</message>
<message>
<source>@VersionMismatch</source>
@ -286,6 +298,14 @@
<source>Delete Server</source>
<translation></translation>
</message>
<message>
<source>LOGIN (Auto-registration)</source>
<translation></translation>
</message>
<message>
<source>Remove from Favorites</source>
<translation></translation>
</message>
</context>
<context>
@ -549,8 +569,7 @@
<source>tutor_msg_3</source>
<translation>&lt;br>
&lt;br>
-> &lt;br>
IP是175.178.66.93</translation>
-> </translation>
</message>
<message>
<source>tutor_msg_4</source>
@ -561,8 +580,8 @@
</message>
<message>
<source>tutor_msg_5</source>
<translation>pdf
pdf都是由开发者们编写
<translation>&lt;a href="https://fkbook-all-in-one.readthedocs.io">https://fkbook-all-in-one.readthedocs.io&lt;/a>&lt;br>。
</translation>
</message>
<message>