1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
//! This module introduces struct [`BmpObjects`], which manages definitions and events of BGA (Background Animation) and BGI (Background Image).
//!
//! BGA includes layers for base images, overlay images, and poor (miss) images. This module also handles
//! extended BGA commands like `#BGA`, `#@BGA`, `#SWBGA`, `#ARGB`, and opacity/color changes.
use std::{
collections::{BTreeMap, HashMap, btree_map::Entry},
path::PathBuf,
};
use crate::bms::{
command::graphics::{Argb, PixelPoint, PixelSize},
parse::{Result, prompt::ChannelDuplication},
prelude::*,
};
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// This aggregate manages definitions and events of BGA (Background Animation) and BGI (Background Image).
pub struct BmpObjects {
/// The BMP file paths corresponding to the id of the background image/video object.
pub bmp_files: HashMap<ObjId, Bmp>,
/// The display mode for background image/video.
pub poor_bga_mode: PoorMode,
/// BGA change events, indexed by time. `#BGA`, `#BGAPOOR`, `#BGALAYER`
pub bga_changes: BTreeMap<ObjTime, BgaObj>,
/// The path of image, which is shown when the player got POOR.
/// This image is displayed when the player misses a note or gets a poor judgment.
pub poor_bmp: Option<PathBuf>,
/// Storage for `#@BGAxx` definitions
pub atbga_defs: HashMap<ObjId, AtBgaDef>,
/// Storage for `#BGAxx` definitions
pub bga_defs: HashMap<ObjId, BgaDef>,
/// SWBGA events, indexed by [`ObjId`]. `#SWBGAxx`
pub swbga_events: HashMap<ObjId, SwBgaEvent>,
/// ARGB definitions, indexed by [`ObjId`]. `#ARGBxx`
pub argb_defs: HashMap<ObjId, Argb>,
/// BGA opacity change events, indexed by time. `#xxx0B:`, `#xxx0C:`, `#xxx0D:`, `#xxx0E:`
pub bga_opacity_changes: HashMap<BgaLayer, BTreeMap<ObjTime, BgaOpacityObj>>,
/// BGA color change events, indexed by time. `#xxxA1:`, `#xxxA2:`, `#xxxA3:`, `#xxxA4:`
pub bga_argb_changes: HashMap<BgaLayer, BTreeMap<ObjTime, BgaArgbObj>>,
/// BGA keybound events, indexed by time. `#xxxA5:`
pub bga_keybound_events: BTreeMap<ObjTime, BgaKeyboundObj>,
}
impl BmpObjects {
/// Gets the time of the last BGA change object.
#[must_use]
pub fn last_obj_time(&self) -> Option<ObjTime> {
self.bga_changes.last_key_value().map(|(&time, _)| time)
}
}
impl BmpObjects {
/// Adds a new bga change object to the notes.
///
/// # Errors
///
/// Returns [`ParseWarning`] if a conflict is found and the
/// provided [`Prompter`] decides to treat it as an error.
pub fn push_bga_change(
&mut self,
bga: BgaObj,
channel: Channel,
prompter: &impl Prompter,
) -> Result<()> {
match self.bga_changes.entry(bga.time) {
Entry::Vacant(entry) => {
entry.insert(bga);
Ok(())
}
Entry::Occupied(mut entry) => {
let existing = entry.get();
prompter
.handle_channel_duplication(ChannelDuplication::BgaChangeEvent {
time: bga.time,
older: existing,
newer: &bga,
})
.apply_channel(entry.get_mut(), bga, bga.time, channel)
}
}
}
/// Adds a new BGA opacity change object to the graphics.
///
/// # Errors
///
/// Returns [`ParseWarning`] if a conflict is found and the
/// provided [`Prompter`] decides to treat it as an error.
pub fn push_bga_opacity_change(
&mut self,
opacity_obj: BgaOpacityObj,
channel: Channel,
prompter: &impl Prompter,
) -> Result<()> {
let this_layer_map = self
.bga_opacity_changes
.entry(opacity_obj.layer)
.or_default();
match this_layer_map.entry(opacity_obj.time) {
Entry::Vacant(entry) => {
entry.insert(opacity_obj);
Ok(())
}
Entry::Occupied(mut entry) => {
let existing = entry.get();
prompter
.handle_channel_duplication(ChannelDuplication::BgaOpacityChangeEvent {
time: opacity_obj.time,
older: existing,
newer: &opacity_obj,
})
.apply_channel(
entry.get_mut(),
opacity_obj.clone(),
opacity_obj.time,
channel,
)
}
}
}
/// Adds a new BGA ARGB color change object to the graphics.
///
/// # Errors
///
/// Returns [`ParseWarning`] if a conflict is found and the
/// provided [`Prompter`] decides to treat it as an error.
pub fn push_bga_argb_change(
&mut self,
argb_obj: BgaArgbObj,
channel: Channel,
prompter: &impl Prompter,
) -> Result<()> {
let this_layer_map = self.bga_argb_changes.entry(argb_obj.layer).or_default();
match this_layer_map.entry(argb_obj.time) {
Entry::Vacant(entry) => {
entry.insert(argb_obj);
Ok(())
}
Entry::Occupied(mut entry) => {
let existing = entry.get();
prompter
.handle_channel_duplication(ChannelDuplication::BgaArgbChangeEvent {
time: argb_obj.time,
older: existing,
newer: &argb_obj,
})
.apply_channel(entry.get_mut(), argb_obj.clone(), argb_obj.time, channel)
}
}
}
/// Adds a new BGA keybound object to the notes.
///
/// # Errors
///
/// Returns [`ParseWarning`] if a conflict is found and the
/// provided [`Prompter`] decides to treat it as an error.
pub fn push_bga_keybound_event(
&mut self,
keybound_obj: BgaKeyboundObj,
prompter: &impl Prompter,
) -> Result<()> {
match self.bga_keybound_events.entry(keybound_obj.time) {
Entry::Vacant(entry) => {
entry.insert(keybound_obj);
Ok(())
}
Entry::Occupied(mut entry) => {
let existing = entry.get();
prompter
.handle_channel_duplication(ChannelDuplication::BgaKeyboundEvent {
time: keybound_obj.time,
older: existing,
newer: &keybound_obj,
})
.apply_channel(
entry.get_mut(),
keybound_obj.clone(),
keybound_obj.time,
Channel::BgaKeybound,
)
}
}
}
}
/// A background image/video data.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Bmp {
/// The path to the image/video file. This is relative path from the BMS file.
pub file: PathBuf,
/// The color which should to be treated as transparent. It should be used only if `file` is an image.
pub transparent_color: Argb,
}
/// A definition for `#@BGA` command.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AtBgaDef {
/// The object ID.
pub id: ObjId,
/// The source BMP object ID.
pub source_bmp: ObjId,
/// The top-left position for trimming in pixels.
pub trim_top_left: PixelPoint,
/// The size for trimming in pixels.
pub trim_size: PixelSize,
/// The draw point position in pixels.
pub draw_point: PixelPoint,
}
/// A definition for `#BGA` command.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BgaDef {
/// The object ID.
pub id: ObjId,
/// The source BMP object ID.
pub source_bmp: ObjId,
/// The top-left position for trimming in pixels.
pub trim_top_left: PixelPoint,
/// The bottom-right position for trimming in pixels.
pub trim_bottom_right: PixelPoint,
/// The draw point position in pixels.
pub draw_point: PixelPoint,
}