Skip to content

Commit af0f2e6

Browse files
feat: add custom font choose and apply
1 parent 92bd90b commit af0f2e6

File tree

10 files changed

+129
-55
lines changed

10 files changed

+129
-55
lines changed

packages/webgal/src/Core/gameScripts/choose/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import ReactDOM from 'react-dom';
66
import React from 'react';
77
import styles from './choose.module.scss';
88
import { webgalStore } from '@/store/store';
9-
import { textFont } from '@/store/userDataInterface';
109
import { PerformController } from '@/Core/Modules/perform/performController';
1110
import { useSEByWebgalStore } from '@/hooks/useSoundEffect';
1211
import { WebGAL } from '@/Core/WebGAL';
1312
import { whenChecker } from '@/Core/controller/gamePlay/scriptExecutor';
1413
import useEscape from '@/hooks/useEscape';
1514
import useApplyStyle from '@/hooks/useApplyStyle';
1615
import { Provider } from 'react-redux';
16+
import { useFontFamily } from '@/hooks/useFontFamily';
1717

1818
class ChooseOption {
1919
/**
@@ -81,8 +81,7 @@ export const choose = (sentence: ISentence): IPerform => {
8181
};
8282

8383
function Choose(props: { chooseOptions: ChooseOption[] }) {
84-
const fontFamily = webgalStore.getState().userData.optionData.textboxFont;
85-
const font = fontFamily === textFont.song ? '"思源宋体", serif' : '"WebgalUI", serif';
84+
const font = useFontFamily();
8685
const { playSeEnter, playSeClick } = useSEByWebgalStore();
8786
const applyStyle = useApplyStyle('Stage/Choose/choose.scss');
8887
// 运行时计算JSX.Element[]

packages/webgal/src/Core/gameScripts/getUserInput/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import ReactDOM from 'react-dom';
66
import React from 'react';
77
import styles from './getUserInput.module.scss';
88
import { webgalStore } from '@/store/store';
9-
import { textFont } from '@/store/userDataInterface';
109
import { PerformController } from '@/Core/Modules/perform/performController';
1110
import { useSEByWebgalStore } from '@/hooks/useSoundEffect';
1211
import { WebGAL } from '@/Core/WebGAL';
1312
import { getStringArgByKey } from '@/Core/util/getSentenceArg';
1413
import { nextSentence } from '@/Core/controller/gamePlay/nextSentence';
1514
import { setStageVar } from '@/store/stageReducer';
15+
import { getCurrentFontFamily } from '@/hooks/useFontFamily';
1616

1717
/**
1818
* 显示选择枝
@@ -27,8 +27,7 @@ export const getUserInput = (sentence: ISentence): IPerform => {
2727
buttonText = buttonText === '' ? 'OK' : buttonText;
2828
const defaultValue = getStringArgByKey(sentence, 'defaultValue');
2929

30-
const fontFamily = webgalStore.getState().userData.optionData.textboxFont;
31-
const font = fontFamily === textFont.song ? '"思源宋体", serif' : '"WebgalUI", serif';
30+
const font = getCurrentFontFamily();
3231

3332
const { playSeEnter, playSeClick } = useSEByWebgalStore();
3433
const chooseElements = (

packages/webgal/src/Core/util/coreInitialFunction/templateLoader.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import axios from 'axios';
22
import { logger } from '@/Core/util/logger';
33
import { WebGAL } from '@/Core/WebGAL';
44
import { TemplateFontDescriptor, WebgalTemplate } from '@/types/template';
5+
import { buildFontOptionsFromTemplate } from '@/Core/util/fonts/fontOptions';
6+
import { webgalStore } from '@/store/store';
7+
import { setFontOptions } from '@/store/GUIReducer';
8+
import { setOptionData } from '@/store/userDataReducer';
59

610
const TEMPLATE_PATH = './game/template/template.json';
711
const TEMPLATE_FONT_STYLE_SELECTOR = 'style[data-webgal-template-fonts]';
@@ -10,14 +14,27 @@ export async function loadTemplate(): Promise<WebgalTemplate | null> {
1014
try {
1115
const { data } = await axios.get<WebgalTemplate>(TEMPLATE_PATH);
1216
WebGAL.template = data;
13-
injectTemplateFonts(data.fonts ?? []);
17+
const fonts = data.fonts ?? [];
18+
injectTemplateFonts(fonts);
19+
updateFontOptions(fonts);
1420
return data;
1521
} catch (error) {
1622
logger.warn('加载模板文件失败', error);
23+
updateFontOptions([]);
1724
return null;
1825
}
1926
}
2027

28+
function updateFontOptions(fonts: TemplateFontDescriptor[]): void {
29+
const options = buildFontOptionsFromTemplate(fonts);
30+
webgalStore.dispatch(setFontOptions(options));
31+
const currentIndex = webgalStore.getState().userData.optionData.textboxFont ?? 0;
32+
if (options.length === 0) return;
33+
if (currentIndex >= options.length) {
34+
webgalStore.dispatch(setOptionData({ key: 'textboxFont', value: 0 }));
35+
}
36+
}
37+
2138
function injectTemplateFonts(fonts: TemplateFontDescriptor[]): void {
2239
if (!fonts.length) return;
2340
const rules = fonts.map((font) => generateFontFaceRule(font)).filter((rule): rule is string => Boolean(rule));
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { FontOption } from '@/store/guiInterface';
2+
import { TemplateFontDescriptor } from '@/types/template';
3+
4+
export const DEFAULT_FONT_OPTIONS: FontOption[] = [
5+
{
6+
family: `'思源宋体', serif`,
7+
source: 'default',
8+
labelKey: 'textFont.options.siYuanSimSun',
9+
},
10+
{
11+
family: `'WebgalUI', serif`,
12+
source: 'default',
13+
labelKey: 'textFont.options.SimHei',
14+
},
15+
{
16+
family: `'LXGW', serif`,
17+
source: 'default',
18+
labelKey: 'textFont.options.lxgw',
19+
},
20+
];
21+
22+
export const FALLBACK_FONT_FAMILY = DEFAULT_FONT_OPTIONS[1].family;
23+
24+
export function buildFontOptionsFromTemplate(fonts: TemplateFontDescriptor[]): FontOption[] {
25+
const templateOptions: FontOption[] = fonts.map((font) => ({
26+
family: formatFontFamily(font['font-family']),
27+
source: 'template',
28+
label: font['font-family'],
29+
}));
30+
31+
const combined = [...templateOptions, ...DEFAULT_FONT_OPTIONS];
32+
33+
const seen = new Set<string>();
34+
return combined.filter((option) => {
35+
if (seen.has(option.family)) return false;
36+
seen.add(option.family);
37+
return true;
38+
});
39+
}
40+
41+
export function formatFontFamily(fontFamily: string): string {
42+
const trimmed = fontFamily.trim();
43+
const needsQuote = /\s/.test(trimmed);
44+
const normalized = needsQuote ? `'${trimmed}'` : trimmed;
45+
return `${normalized}, serif`;
46+
}

packages/webgal/src/UI/Menu/Options/Display/Display.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import styles from '@/UI/Menu/Options/options.module.scss';
66
import useFullScreen from '@/hooks/useFullScreen';
77
import useTrans from '@/hooks/useTrans';
88
import { RootState } from '@/store/store';
9-
import { textFont, textSize } from '@/store/userDataInterface';
9+
import { textSize } from '@/store/userDataInterface';
1010
import { setOptionData } from '@/store/userDataReducer';
1111
import { useDispatch, useSelector } from 'react-redux';
1212
import { OptionSlider } from '../OptionSlider';
@@ -16,6 +16,15 @@ export function Display() {
1616
const dispatch = useDispatch();
1717
const t = useTrans('menu.options.pages.display.options.');
1818
const { isSupported: isFullscreenSupported, enter: enterFullscreen, exit: exitFullscreen } = useFullScreen();
19+
const fontOptions = useSelector((state: RootState) => state.GUI.fontOptions);
20+
const fontOptionTexts = fontOptions.map((option) => {
21+
if (option.labelKey) return t(option.labelKey);
22+
if (option.label) return option.label;
23+
return option.family;
24+
});
25+
const currentFontIndex = fontOptions.length
26+
? Math.min(userDataState.optionData.textboxFont, fontOptions.length - 1)
27+
: 0;
1928

2029
return (
2130
<div className={styles.Options_main_content_half}>
@@ -50,22 +59,12 @@ export function Display() {
5059
</NormalOption>
5160
<NormalOption key="textFont" title={t('textFont.title')}>
5261
<NormalButton
53-
textList={t('textFont.options.siYuanSimSun', 'textFont.options.SimHei', 'textFont.options.lxgw')}
54-
functionList={[
55-
() => {
56-
dispatch(setOptionData({ key: 'textboxFont', value: textFont.song }));
57-
setStorage();
58-
},
59-
() => {
60-
dispatch(setOptionData({ key: 'textboxFont', value: textFont.hei }));
61-
setStorage();
62-
},
63-
() => {
64-
dispatch(setOptionData({ key: 'textboxFont', value: textFont.lxgw }));
65-
setStorage();
66-
},
67-
]}
68-
currentChecked={userDataState.optionData.textboxFont}
62+
textList={fontOptionTexts}
63+
functionList={fontOptions.map((_, index) => () => {
64+
dispatch(setOptionData({ key: 'textboxFont', value: index }));
65+
setStorage();
66+
})}
67+
currentChecked={currentFontIndex}
6968
/>
7069
</NormalOption>
7170
<NormalOption key="textSpeed" title={t('textSpeed.title')}>
Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import { useSelector } from 'react-redux';
2-
import { RootState } from '@/store/store';
3-
import { textFont } from '@/store/userDataInterface';
4-
import { match } from '@/Core/util/match';
2+
import { RootState, webgalStore } from '@/store/store';
3+
import { FALLBACK_FONT_FAMILY } from '@/Core/util/fonts/fontOptions';
54

6-
export function useFontFamily() {
7-
const fontFamily = useSelector((state: RootState) => state.userData.optionData.textboxFont);
5+
export function useFontFamily(): string {
6+
return useSelector(selectFontFamily);
7+
}
88

9-
function getFont() {
10-
return match(fontFamily)
11-
.with(textFont.song, () => '"思源宋体", serif')
12-
.with(textFont.lxgw, () => '"LXGW", serif')
13-
.with(textFont.hei, () => '"WebgalUI", serif')
14-
.default(() => '"WebgalUI", serif');
15-
}
9+
export function getCurrentFontFamily(): string {
10+
return selectFontFamily(webgalStore.getState());
11+
}
1612

17-
return getFont();
13+
export function selectFontFamily(state: RootState): string {
14+
const index = state.userData.optionData.textboxFont ?? 0;
15+
const fonts = state.GUI.fontOptions;
16+
if (fonts[index]) {
17+
return fonts[index].family;
18+
}
19+
if (fonts.length > 0) {
20+
return fonts[0].family;
21+
}
22+
return FALLBACK_FONT_FAMILY;
1823
}

packages/webgal/src/store/GUIReducer.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
/**
2-
* @file 记录当前GUI的状态信息,引擎初始化时会重置。
3-
* @author Mahiru
4-
*/
51
import { getStorage } from '@/Core/controller/storage/storageController';
6-
import { GuiAsset, IGuiState, MenuPanelTag, setAssetPayload, setVisibilityPayload } from '@/store/guiInterface';
2+
import {
3+
FontOption,
4+
GuiAsset,
5+
IGuiState,
6+
MenuPanelTag,
7+
setAssetPayload,
8+
setVisibilityPayload,
9+
} from '@/store/guiInterface';
710
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
8-
import { key } from 'localforage';
11+
import { DEFAULT_FONT_OPTIONS } from '@/Core/util/fonts/fontOptions';
912

1013
/**
1114
* 初始GUI状态表
1215
*/
1316
const initState: IGuiState = {
17+
fontOptions: [...DEFAULT_FONT_OPTIONS],
1418
showBacklog: false,
1519
showStarter: true,
1620
showTitle: true,
@@ -80,6 +84,9 @@ const GUISlice = createSlice({
8084
setFontOptimization: (state, action: PayloadAction<boolean>) => {
8185
state.fontOptimization = action.payload;
8286
},
87+
setFontOptions: (state, action: PayloadAction<FontOption[]>) => {
88+
state.fontOptions = [...action.payload];
89+
},
8390
},
8491
});
8592

@@ -90,6 +97,7 @@ export const {
9097
setLogoImage,
9198
setEnableAppreciationMode,
9299
setFontOptimization,
100+
setFontOptions,
93101
} = GUISlice.actions;
94102
export default GUISlice.reducer;
95103

packages/webgal/src/store/guiInterface.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { IWebGalTextBoxTheme } from '@/Stage/themeInterface';
2-
31
/**
42
* 当前Menu页面显示的Tag
53
*/
@@ -13,6 +11,7 @@ export enum MenuPanelTag {
1311
* @interface IGuiState GUI状态接口
1412
*/
1513
export interface IGuiState {
14+
fontOptions: FontOption[];
1615
showStarter: boolean; // 是否显示初始界面(用于使得bgm可以播放)
1716
showTitle: boolean; // 是否显示标题界面
1817
showMenuPanel: boolean; // 是否显示Menu界面
@@ -35,7 +34,7 @@ export interface IGuiState {
3534

3635
export type componentsVisibility = Pick<
3736
IGuiState,
38-
Exclude<keyof IGuiState, 'currentMenuTag' | 'titleBg' | 'titleBgm' | 'logoImage' | 'theme'>
37+
Exclude<keyof IGuiState, 'currentMenuTag' | 'titleBg' | 'titleBgm' | 'logoImage' | 'theme' | 'fontOptions'>
3938
>;
4039
// 标题资源
4140
export type GuiAsset = Pick<IGuiState, 'titleBgm' | 'titleBg'>;
@@ -58,3 +57,12 @@ export interface setAssetPayload {
5857
}
5958

6059
export type GuiStore = IGuiStore;
60+
61+
export type FontOptionSource = 'default' | 'template';
62+
63+
export interface FontOption {
64+
family: string;
65+
source: FontOptionSource;
66+
labelKey?: string;
67+
label?: string;
68+
}

packages/webgal/src/store/userDataInterface.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,6 @@ export enum textSize {
1818
large,
1919
}
2020

21-
export enum textFont {
22-
song,
23-
hei,
24-
lxgw,
25-
}
26-
2721
export enum voiceOption {
2822
yes,
2923
no,
@@ -47,7 +41,7 @@ export interface IOptionData {
4741
seVolume: number; // 音效音量
4842
uiSeVolume: number; // 用户界面音效音量
4943
slPage: number; // 存读档界面所在页面
50-
textboxFont: textFont;
44+
textboxFont: number;
5145
textboxOpacity: number;
5246
language: language;
5347
voiceInterruption: voiceOption; // 是否中断语音

packages/webgal/src/store/userDataReducer.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
IUserData,
1414
fullScreenOption,
1515
playSpeed,
16-
textFont,
1716
textSize,
1817
voiceOption,
1918
} from '@/store/userDataInterface';
@@ -31,7 +30,7 @@ const initialOptionSet: IOptionData = {
3130
bgmVolume: 25, // 背景音乐音量
3231
seVolume: 100, // 音效音量
3332
uiSeVolume: 50, // UI音效音量
34-
textboxFont: textFont.song,
33+
textboxFont: 0,
3534
textboxOpacity: 75,
3635
language: language.zhCn,
3736
voiceInterruption: voiceOption.yes,

0 commit comments

Comments
 (0)