Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions lib/const/abilities.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,15 @@ class BaseAbility extends Ability {
class ImageAbility extends Ability {
final String imagePath;
final double size;
final Color? inactiveTraceColor;

ImageAbility({required this.imagePath, required this.size});
ImageAbility({
required this.imagePath,
required this.size,
this.inactiveTraceColor,
});

bool get supportsInactiveState => inactiveTraceColor != null;

@override
Widget createWidget({
Expand All @@ -141,6 +148,10 @@ class ImageAbility extends Ability {
imagePath: imagePath,
size: size * mapScale,
id: id,
visualState: visualState,
inactiveTraceColor: inactiveTraceColor,
watchMouse: watchMouse,
contextMenuItems: contextMenuItems,
);
}

Expand Down Expand Up @@ -343,6 +354,7 @@ class SquareAbility extends Ability {
final bool hasTopborder;
final bool hasSideBorders;
final bool isTransparent;
final bool supportsInactiveState;

SquareAbility({
required this.width,
Expand All @@ -354,8 +366,12 @@ class SquareAbility extends Ability {
this.hasTopborder = false,
this.hasSideBorders = false,
this.isTransparent = false,
this.supportsInactiveState = false,
double? minHeight,
});
}) : assert(
!supportsInactiveState || isWall,
'Inactive square traces are only supported for walls',
);

@override
Offset getAnchorPoint({
Expand Down Expand Up @@ -430,6 +446,7 @@ class SquareAbility extends Ability {
hasSideBorders: hasSideBorders,
isWall: isWall,
isTransparent: isTransparent,
supportsInactiveState: supportsInactiveState,
visualState: visualState,
watchMouse: watchMouse,
contextMenuItems: contextMenuItems,
Expand Down
2 changes: 2 additions & 0 deletions lib/const/agents.dart
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ class AgentData implements DraggableData {
abilityData: ImageAbility(
imagePath: 'assets/agents/Viper/Smoke.webp',
size: 4.5 * inGameMetersDiameter,
inactiveTraceColor: Colors.greenAccent,
),
);

Expand All @@ -380,6 +381,7 @@ class AgentData implements DraggableData {
width: 5,
height: 60 * inGameMeters,
isWall: true,
supportsInactiveState: true,
iconPath: agent.abilities[2].iconPath,
color: Colors.greenAccent,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,22 @@ import 'package:icarus/widgets/draggable_widgets/adjacent_page_copy_menu.dart';
import 'package:shadcn_ui/shadcn_ui.dart';

bool supportsAbilityVisibilityMenu(Ability? ability) {
return ability is SquareAbility ||
return supportsAbilityInactiveState(ability) ||
ability is SquareAbility ||
ability is CenterSquareAbility ||
ability is CircleAbility ||
ability is SectorCircleAbility ||
ability is DeadlockBarrierMeshAbility;
}

bool supportsAbilityInactiveState(Ability? ability) {
return switch (ability) {
ImageAbility() => ability.supportsInactiveState,
SquareAbility() => ability.supportsInactiveState,
_ => false,
};
}

List<ShadContextMenuItem>? buildAbilityContextMenuItems(
WidgetRef ref,
PlacedAbility ability, {
Expand Down Expand Up @@ -77,6 +86,18 @@ List<_AbilityVisibilityControl> _buildVisibilityControls(
Ability? abilityData,
AbilityVisualState visualState,
) {
if (supportsAbilityInactiveState(abilityData)) {
return [
_AbilityVisibilityControl(
label: 'Active',
isEnabled: visualState.showRangeFill,
toggle: (state) => state.copyWith(
showRangeFill: !state.showRangeFill,
),
),
];
}

if (abilityData is CircleAbility || abilityData is SectorCircleAbility) {
if (_hasInnerRange(abilityData)) {
return [
Expand Down
41 changes: 35 additions & 6 deletions lib/widgets/draggable_widgets/ability/custom_square_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:icarus/const/placed_classes.dart';
import 'package:icarus/providers/strategy_settings_provider.dart';
import 'package:icarus/widgets/custom_border_container.dart';
import 'package:icarus/widgets/draggable_widgets/ability/ability_widget.dart';
import 'package:icarus/widgets/draggable_widgets/ability/inactive_ability_trace.dart';
import 'package:shadcn_ui/shadcn_ui.dart';

class CustomSquareWidget extends ConsumerWidget {
Expand All @@ -24,6 +25,7 @@ class CustomSquareWidget extends ConsumerWidget {
required this.hasSideBorders,
required this.isWall,
required this.isTransparent,
this.supportsInactiveState = false,
this.visualState,
this.watchMouse = true,
this.contextMenuItems,
Expand All @@ -43,6 +45,7 @@ class CustomSquareWidget extends ConsumerWidget {
final bool hasSideBorders;
final bool isWall;
final bool isTransparent;
final bool supportsInactiveState;
final AbilityVisualState? visualState;
final bool watchMouse;
final List<ShadContextMenuItem>? contextMenuItems;
Expand All @@ -67,6 +70,14 @@ class CustomSquareWidget extends ConsumerWidget {
(scaledAbilitySize / 2) +
resizeButtonOffset;
final showRangeFill = visualState?.showRangeFill ?? true;
final inactiveTraceWidth = coordinateSystem.scale(12);
final wallBody = IgnorePointer(
child: Container(
width: width,
height: scaledHeight,
color: abilityWallDisplayColor(color),
),
);

return SizedBox(
width: scaledWidth,
Expand Down Expand Up @@ -111,14 +122,32 @@ class CustomSquareWidget extends ConsumerWidget {
Positioned(
top: resizeButtonOffset,
left: (scaledWidth / 2) - width / 2,
child: Opacity(
key: const ValueKey('square-range-body'),
opacity: showRangeFill ? 1 : 0,
child: supportsInactiveState
? AnimatedOpacity(
key: const ValueKey('square-range-body-transition'),
opacity: showRangeFill ? 1 : 0,
duration: abilityStateTransitionDuration,
child: wallBody,
)
: Opacity(
key: const ValueKey('square-range-body'),
opacity: showRangeFill ? 1 : 0,
child: wallBody,
),
),
if (isWall && supportsInactiveState)
Positioned(
top: resizeButtonOffset,
left: (scaledWidth - inactiveTraceWidth) / 2,
child: AnimatedOpacity(
key: const ValueKey('inactive-wall-trace-transition'),
opacity: showRangeFill ? 0 : 1,
duration: abilityStateTransitionDuration,
child: IgnorePointer(
child: Container(
width: width,
child: SizedBox(
width: inactiveTraceWidth,
height: scaledHeight,
color: color.withAlpha(100),
child: InactiveWallAbilityTrace(color: color),
),
),
),
Expand Down
126 changes: 126 additions & 0 deletions lib/widgets/draggable_widgets/ability/inactive_ability_trace.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import 'dart:math' as math;

import 'package:flutter/material.dart';

const abilityStateTransitionDuration = Duration(milliseconds: 180);

Color abilityWallDisplayColor(Color color) => color.withAlpha(100);

class InactiveCircleAbilityTrace extends StatelessWidget {
const InactiveCircleAbilityTrace({super.key, required this.color});

final Color color;

@override
Widget build(BuildContext context) {
return CustomPaint(
key: const ValueKey('inactive-circle-trace'),
painter: InactiveCircleAbilityTracePainter(
color: abilityWallDisplayColor(color),
),
);
}
}

class InactiveWallAbilityTrace extends StatelessWidget {
const InactiveWallAbilityTrace({super.key, required this.color});

final Color color;

@override
Widget build(BuildContext context) {
return CustomPaint(
key: const ValueKey('inactive-wall-trace'),
painter: InactiveWallAbilityTracePainter(
color: abilityWallDisplayColor(color),
),
);
}
}

class InactiveCircleAbilityTracePainter extends CustomPainter {
const InactiveCircleAbilityTracePainter({required this.color});

final Color color;

@override
void paint(Canvas canvas, Size size) {
const strokeWidth = 2.0;
final paint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;
const inset = strokeWidth / 2;
final boundary = Path()
..addOval(
Rect.fromLTWH(
inset,
inset,
math.max(0, size.width - strokeWidth),
math.max(0, size.height - strokeWidth),
),
);

_drawDashedPath(canvas, boundary, paint);

canvas.drawCircle(
size.center(Offset.zero),
3,
Paint()
..color = color
..style = PaintingStyle.fill,
);
}

@override
bool shouldRepaint(InactiveCircleAbilityTracePainter oldDelegate) {
return oldDelegate.color != color;
}
}

class InactiveWallAbilityTracePainter extends CustomPainter {
const InactiveWallAbilityTracePainter({required this.color});

final Color color;

@override
void paint(Canvas canvas, Size size) {
const strokeWidth = 2.0;
const inset = strokeWidth / 2;
final start = Offset(size.width / 2, inset);
final end = Offset(size.width / 2, size.height - inset);
final path = Path()
..moveTo(start.dx, start.dy)
..lineTo(end.dx, end.dy);
final tracePaint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..strokeCap = StrokeCap.round;

_drawDashedPath(canvas, path, tracePaint);
}

@override
bool shouldRepaint(InactiveWallAbilityTracePainter oldDelegate) {
return oldDelegate.color != color;
}
}

void _drawDashedPath(
Canvas canvas,
Path path,
Paint paint, {
double dashLength = 8,
double gapLength = 5,
}) {
for (final metric in path.computeMetrics()) {
double distance = 0;
while (distance < metric.length) {
final end = math.min(distance + dashLength, metric.length);
canvas.drawPath(metric.extractPath(distance, end), paint);
distance = end + gapLength;
}
}
}
Loading
Loading