-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDSACharacterPortraitView.m
More file actions
340 lines (268 loc) · 12.7 KB
/
DSACharacterPortraitView.m
File metadata and controls
340 lines (268 loc) · 12.7 KB
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
/*
Project: DSA-SpielHelfer
Copyright (C) 2025 Free Software Foundation
Author: Sebastian Reitenbach
Created: 2025-02-28 21:40:13 +0100 by sebastia
This application 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 application 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
Library General Public License for more details.
You should have received a copy of the GNU General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/
#import "DSACharacterPortraitView.h"
#import "DSACharacterDocument.h"
#import "DSAAdventureDocument.h"
#import "DSAAdventureWindowController.h"
#import "DSACharacter.h"
extern NSString * const DSACharacterHighlightedNotification;
@implementation DSACharacterPortraitView
static DSACharacterPortraitView *currentlyHighlightedView = nil;
static NSString * const DSACharacterDragType = @"DSACharacterDragType";
- (void)awakeFromNib {
[super awakeFromNib];
// Register this view as a drag destination
[self registerForDraggedTypes:@[DSACharacterDragType]];
}
- (void)mouseDown:(NSEvent *)event {
if (event.clickCount == 2) {
NSLog(@"DSACharacterPortraitView doubleClick characterDocument: %@", [self.characterDocument class]);
if (self.characterDocument) {
[self.characterDocument showCharacterWindow];
}
return; // Prevent further processing for double-clicks
}
// Store the initial click location
self.initialClickLocation = [self convertPoint:event.locationInWindow fromView:nil];
// Schedule highlighting with a delay to differentiate from drag
[self performSelector:@selector(handleClickHighlight) withObject:nil afterDelay:0.1];
[super mouseDown:event];
}
- (void)handleClickHighlight {
// If dragging started, cancel highlighting
NSPoint currentLocation = [self.window mouseLocationOutsideOfEventStream];
currentLocation = [self convertPoint:currentLocation fromView:nil];
CGFloat distance = hypot(currentLocation.x - self.initialClickLocation.x,
currentLocation.y - self.initialClickLocation.y);
if (distance > 5) { // Threshold to detect drag
return; // Do not highlight if dragging started
}
// Handle selection highlighting (only if not dragging)
if (self == currentlyHighlightedView) {
[self highlightTargetView:NO];
currentlyHighlightedView = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:DSACharacterHighlightedNotification
object: nil
userInfo:nil];
} else {
[currentlyHighlightedView highlightTargetView:NO];
currentlyHighlightedView = self;
[self highlightTargetView:YES];
[[NSNotificationCenter defaultCenter] postNotificationName:DSACharacterHighlightedNotification
object:self.characterDocument
userInfo:nil];
}
}
- (void)mouseDragged:(NSEvent *)event {
if (!self.characterDocument || !self.characterDocument.model) {
return; // No valid character to drag
}
NSUUID *modelID = self.characterDocument.model.modelID; // Get the UUID
// Use the pasteboard for drag-and-drop
NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
[pboard declareTypes:@[DSACharacterDragType] owner:self];
[pboard setString:[modelID UUIDString] forType:DSACharacterDragType]; // Store modelID
// Scale the image to 64x64 before dragging
NSImage *dragImage = self.image ?: [[NSImage alloc] initWithSize:self.bounds.size];
if (dragImage) {
NSImage *scaledImage = [[NSImage alloc] initWithSize:NSMakeSize(64, 64)];
[scaledImage lockFocus];
#ifdef GNUSTEP
// GNUstep alternative: Scale image manually using setSize before drawing
NSSize originalSize = dragImage.size;
[dragImage setSize:NSMakeSize(64, 64)]; // Scale image
[dragImage compositeToPoint:NSMakePoint(0, 0) operation:NSCompositeSourceOver];
[dragImage setSize:originalSize]; // Restore original size
#else
// macOS version
[dragImage drawInRect:NSMakeRect(0, 0, 64, 64)
fromRect:NSMakeRect(0, 0, dragImage.size.width, dragImage.size.height)
operation:NSCompositingOperationSourceOver
fraction:1.0];
#endif
[scaledImage unlockFocus];
dragImage = scaledImage; // Use the resized image
}
// Get drag position
NSPoint dragPosition = [self convertPoint:event.locationInWindow fromView:nil];
// Start drag operation with resized image
[self dragImage:dragImage
at:dragPosition
offset:NSZeroSize
event:event
pasteboard:pboard
source:self
slideBack:YES];
}
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
NSPasteboard *pboard = [sender draggingPasteboard];
NSString *draggedModelID = [pboard stringForType:DSACharacterDragType];
if (draggedModelID) {
DSAAdventureWindowController *windowController = (DSAAdventureWindowController *)self.window.windowController;
DSAAdventureDocument *adventureDoc = (DSAAdventureDocument *)windowController.document;
NSMutableArray *characters = [adventureDoc.characterDocuments mutableCopy];
// Find the dragged character and the target character
DSACharacterDocument *draggedCharacter = nil;
DSACharacterDocument *targetCharacter = self.characterDocument;
for (DSACharacterDocument *charDoc in characters) {
if ([charDoc.model.modelID isEqual:[[NSUUID alloc] initWithUUIDString: draggedModelID]]) {
draggedCharacter = charDoc;
break;
}
}
if (draggedCharacter && targetCharacter && draggedCharacter != targetCharacter) {
NSUInteger draggedIndex = [characters indexOfObject:draggedCharacter];
NSUInteger targetIndex = [characters indexOfObject:targetCharacter];
[characters exchangeObjectAtIndex:draggedIndex withObjectAtIndex:targetIndex];
[adventureDoc updateChangeCount: NSChangeDone];
// Update the document’s character order
adventureDoc.characterDocuments = characters;
// Refresh UI
[[NSNotificationCenter defaultCenter] postNotificationName:@"DSAAdventureCharactersUpdated" object:self];
//[windowController handleCharacterChanges]; // replaced with above
return YES;
}
}
return NO;
}
- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal {
NSLog(@"DSACharacterPortraitView: draggingSourceOperationMaskForLocal: %@", isLocal ? @"YES" : @"NO");
// If the drag is within the same app, return NSDragOperationMove
if (isLocal) {
return NSDragOperationMove; // Move is typical for inventory management
}
// If the drag is external (e.g., to another app), allow Copy
return NSDragOperationCopy;
}
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
NSPasteboard *pboard = [sender draggingPasteboard];
if ([pboard availableTypeFromArray:@[DSACharacterDragType]]) {
return NSDragOperationMove; // Allow moving characters
}
return NSDragOperationNone;
}
// Indicate we accept the drop (this is required)
- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)sender {
return YES; // Always return YES to accept the drop
}
// Cleanup after drop (optional)
- (void)concludeDragOperation:(id<NSDraggingInfo>)sender {
NSLog(@"Drag operation completed");
}
- (NSString *)toolTip {
NSLog(@"DSACharacterPortraitView toolTip called");
// Check if we have a valid characterDocument and its model
if (self.characterDocument && self.characterDocument.model) {
DSACharacter *character = self.characterDocument.model;
// Format the tooltip text with the character's name and values
NSString *toolTipText = [NSString stringWithFormat:
@"%@\n"
@"LE: %ld/%ld\n"
@"AE: %ld/%ld\n"
@"KE: %ld/%ld",
character.name, // Character's name
character.currentLifePoints, character.lifePoints, // Life Points
character.currentAstralEnergy, character.astralEnergy, // Astral Energy
character.currentKarmaPoints, character.karmaPoints]; // Karma Points
return toolTipText;
}
return @"";
}
- (void)updateCharacterNameLabel {
// 1. Label finden oder erstellen (Lazy Instantiation)
NSTextField *nameLabel = [self viewWithTag:1002]; // Tag 1002 für den Namen
if (!nameLabel) {
// Start-Frame: Oben links. Die genaue Position wird später berechnet.
nameLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 10, 14)];
// --- Setup nur einmalig ---
nameLabel.backgroundColor = [NSColor blackColor];
nameLabel.drawsBackground = YES;
nameLabel.textColor = [NSColor whiteColor];
// Etwas größerer Font für den Namen
nameLabel.font = [NSFont boldSystemFontOfSize:10];
nameLabel.bordered = NO;
nameLabel.bezeled = NO;
nameLabel.editable = NO;
nameLabel.selectable = NO;
nameLabel.focusRingType = NSFocusRingTypeNone;
nameLabel.tag = 1002;
[self addSubview:nameLabel];
}
// 2. Text setzen und Größe anpassen
if (self.characterDocument && self.characterDocument.model) {
DSACharacter *character = self.characterDocument.model;
nameLabel.stringValue = character.name;
NSDictionary *attrs = @{ NSFontAttributeName: nameLabel.font };
NSSize size = [character.name sizeWithAttributes:attrs];
// Berechnung der Position für die zentrierte Anzeige oben:
// X-Koordinate: (Gesamtbreite - Textbreite) / 2 -> Zentriert
// CGFloat xPos = (self.bounds.size.width - size.width) / 2.0;
// Y-Koordinate: Oben, z.B. 5 Punkte vom oberen Rand.
// CGFloat yPos = self.bounds.size.height - size.height - 5.0;
// Den Frame aktualisieren
nameLabel.frame = NSMakeRect(0, 0, size.width + 4, size.height);
nameLabel.hidden = NO;
} else {
// 3. Label bei leerem Text ausblenden
nameLabel.hidden = YES;
}
}
- (void)highlightTargetView:(BOOL)highlight {
NSColor *highlightColor = highlight ? [NSColor greenColor] : [NSColor clearColor];
// Remove existing highlight view if any
if (self.highlightView) {
[self.highlightView removeFromSuperview];
self.highlightView = nil;
}
if (highlight) {
// Create a new highlight view
self.highlightView = [[NSBox alloc] initWithFrame:self.bounds];
self.highlightView.boxType = NSBoxCustom;
self.highlightView.borderType = NSLineBorder;
self.highlightView.borderColor = highlightColor;
self.highlightView.borderWidth = 2.0;
self.highlightView.fillColor = [NSColor clearColor]; // Transparent fill
self.highlightView.title = nil;
self.highlightView.titlePosition = NSNoTitle;
[self addSubview:self.highlightView positioned:NSWindowAbove relativeTo:nil];
}
}
// Ugly hack to fade out the character portraits
// for some reason, setting alphaValue alone doesn't work
// ChatGPT suggested to use fadeFraction as a separate property instead of
// misusing alphaValue as I do here
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
NSRect insetRect = NSMakeRect(
self.bounds.origin.x + 1, // Left inset 1px
self.bounds.origin.y + 2, // Top inset 1px
self.bounds.size.width - (1 + 2), // width - left(1) - right(2)
self.bounds.size.height - (1 + 2) // height - bottom(1) - top(2)
);
if (self.image) {
[self.image drawInRect:insetRect
fromRect:NSZeroRect
operation: NSCompositeSourceOver
fraction:1.0];
if (self.alphaValue < 1.0) {
[[NSColor colorWithCalibratedWhite:1.0 alpha:(1.0 - self.alphaValue)] set];
NSRectFillUsingOperation(insetRect, NSCompositeSourceOver);
}
}
}
@end