tremble 1.2.5 copy "tremble: ^1.2.5" to clipboard
tremble: ^1.2.5 copied to clipboard

A Simple Flutter game engine.

example/lib/main.dart

import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tremble/tremble.dart';
import 'package:tremble/utils/signal_value.dart';
import 'package:tremble_example/examples/bouncing_shapes_example.dart';
import 'package:tremble_example/examples/input_example.dart';
import 'package:tremble_example/examples/spritebatch_example.dart';
import 'package:tremble_example/examples/tile_map_example.dart';
import 'package:url_launcher/url_launcher.dart';

enum ExampleType { input, rendering, tooling, utility, physics, demo }

class ExampleItem {
  final String title;
  final String description;
  final ExampleType type;
  final String codeUrl;

  final ScreenController Function() screen;

  ExampleItem({
    required this.title,
    required this.description,
    required this.type,
    required this.codeUrl,
    required this.screen,
  });
}

final examples = <ExampleItem>[
  ExampleItem(
    title: "Bouncing Shapes",
    description: "Bunch of random shapes bouncing around.",
    type: ExampleType.demo,
    codeUrl:
        "https://github.com/Descrout/tremble/blob/main/example/lib/examples/bouncing_shapes_example.dart",
    screen: () => BouncingShapesExample(),
  ),
  ExampleItem(
    title: "Input Handling",
    description: "Handle *keyboard* and *mouse/touch* inputs.",
    type: ExampleType.input,
    codeUrl:
        "https://github.com/Descrout/tremble/blob/main/example/lib/examples/input_example.dart",
    screen: () => InputExample(),
  ),
  ExampleItem(
    title: "SpriteBatch",
    description: "Efficiently draw a lot of *Sprite* and *Animation* objects.",
    type: ExampleType.rendering,
    codeUrl:
        "https://github.com/Descrout/tremble/blob/main/example/lib/examples/spritebatch_example.dart",
    screen: () => SpritebatchExample(),
  ),
  ExampleItem(
    title: "Simple Level Editor",
    description: "Simple in-game level editor using *TileMap* and *Grid*.",
    type: ExampleType.demo,
    codeUrl:
        "https://github.com/Descrout/tremble/blob/main/example/lib/examples/tile_map_example.dart",
    screen: () => TileMapExample(),
  ),
];

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  if (kIsWeb) {
    await BrowserContextMenu.disableContextMenu();
  }

  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final exampleIndex = SignalValue<int>(0);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Tremble Examples',
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: const Color(0xFF0f0f1a),
      ),
      home: Scaffold(
        body: SignalValueBuilder(
          value: exampleIndex,
          builder: (context, child, index) {
            return Row(
              children: [
                _buildSidebar(index),
                const VerticalDivider(width: 1, color: Color(0xFF2a2a3e)),
                Expanded(child: _buildGamePanel(index)),
              ],
            );
          },
        ),
      ),
    );
  }

  Widget _buildSidebar(int selectedIndex) {
    return SizedBox(
      width: 360,
      child: Container(
        color: const Color(0xFF14142a),
        child: Column(
          children: [
            _buildSidebarHeader(),
            const Divider(height: 1, color: Color(0xFF2a2a3e)),
            Expanded(
              child: ListView.builder(
                padding: const EdgeInsets.all(12),
                itemCount: examples.length,
                itemBuilder: (context, i) => _buildExampleItem(
                  examples[i],
                  i == selectedIndex,
                  i,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSidebarHeader() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18),
      child: Row(
        children: [
          ClipRRect(
            borderRadius: BorderRadius.circular(8),
            child: Image.asset(
              "assets/tremble_logo.jpeg",
              width: 36,
              height: 36,
              fit: BoxFit.cover,
            ),
          ),
          const SizedBox(width: 14),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  "Tremble Examples",
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.w700,
                    color: Colors.grey[100],
                    letterSpacing: -0.3,
                  ),
                ),
                const SizedBox(height: 2),
                Text(
                  "v1.2.3",
                  style: TextStyle(
                    fontSize: 12,
                    color: Colors.grey[600],
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ],
            ),
          ),
          Material(
            color: Colors.transparent,
            child: InkWell(
              borderRadius: BorderRadius.circular(8),
              onTap: () => launchUrl(Uri.parse("https://tremble-docs.netlify.app")),
              child: Container(
                padding: const EdgeInsets.all(8),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.grey[700]!),
                ),
                child: Icon(Icons.menu_book, size: 18, color: Colors.grey[400]),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildExampleItem(ExampleItem example, bool selected, int index) {
    final typeColor = _typeColor(example.type);
    return Padding(
      padding: const EdgeInsets.only(bottom: 10),
      child: InkWell(
        borderRadius: BorderRadius.circular(12),
        onTap: () => exampleIndex.value = index,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 150),
          padding: const EdgeInsets.all(14),
          decoration: BoxDecoration(
            color: selected ? const Color(0xFF1e1e3f) : Colors.transparent,
            borderRadius: BorderRadius.circular(12),
            border: Border.all(
              color: selected ? typeColor.withValues(alpha: 0.5) : const Color(0xFF2a2a3e),
              width: selected ? 1.5 : 1,
            ),
          ),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _typeIcon(example.type, typeColor),
              const SizedBox(width: 14),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Expanded(
                          child: Text(
                            example.title,
                            style: TextStyle(
                              fontSize: 15,
                              fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
                              color: Colors.grey[100],
                            ),
                          ),
                        ),
                        _typeChip(example.type, typeColor),
                      ],
                    ),
                    const SizedBox(height: 6),
                    RichText(
                      text: TextSpan(
                        style: TextStyle(
                          fontSize: 13,
                          color: Colors.grey[400],
                          height: 1.4,
                        ),
                        children: _parseDescription(
                          example.description,
                          highlightColor: typeColor,
                        ),
                      ),
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                    ),
                    const SizedBox(height: 4),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildGamePanel(int index) {
    final example = examples[index];
    return Column(
      children: [
        _buildGameHeader(example),
        const Divider(height: 1, color: Color(0xFF2a2a3e)),
        Expanded(
          child: FittedBox(
            child: Container(
              width: 800,
              height: 600,
              margin: const EdgeInsets.all(32),
              decoration: BoxDecoration(
                border: Border.all(
                  color: Colors.white,
                  width: 2,
                ),
              ),
              child: GameArea(controller: example.screen()),
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildGameHeader(ExampleItem example) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
      color: const Color(0xFF14142a),
      child: Row(
        children: [
          IconButton(
            onPressed: () => launchUrl(Uri.parse(example.codeUrl)),
            icon: Icon(Icons.code, color: Colors.cyan[400]),
            tooltip: "View Code",
          ),
          const SizedBox(width: 8),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  example.title,
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.w600,
                    color: Colors.white,
                  ),
                ),
                const SizedBox(height: 2),
                RichText(
                  text: TextSpan(
                    style: TextStyle(
                      fontSize: 13,
                      color: Colors.grey[400],
                      height: 1.3,
                    ),
                    children: _parseDescription(
                      example.description,
                      highlightColor: Colors.cyan[400]!,
                    ),
                  ),
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                ),
              ],
            ),
          ),
          TextButton.icon(
            onPressed: () => launchUrl(Uri.parse(example.codeUrl)),
            icon: Icon(Icons.code, size: 18, color: Colors.cyan[400]),
            label: Text(
              "View Code",
              style: TextStyle(fontSize: 14, color: Colors.cyan[400]),
            ),
          ),
        ],
      ),
    );
  }

  Color _typeColor(ExampleType type) {
    return switch (type) {
      ExampleType.input => Colors.orange[400]!,
      ExampleType.rendering => Colors.purple[400]!,
      ExampleType.tooling => Colors.teal[400]!,
      ExampleType.utility => Colors.blue[400]!,
      ExampleType.physics => Colors.red[400]!,
      ExampleType.demo => Colors.green[400]!,
    };
  }

  Widget _typeIcon(ExampleType type, Color color, {double size = 32}) {
    return Container(
      width: size,
      height: size,
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.2),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Icon(
        switch (type) {
          ExampleType.input => Icons.keyboard,
          ExampleType.rendering => Icons.image,
          ExampleType.tooling => Icons.build,
          ExampleType.utility => Icons.handyman,
          ExampleType.physics => Icons.bolt,
          ExampleType.demo => Icons.play_circle,
        },
        size: size * 0.55,
        color: color,
      ),
    );
  }

  Widget _typeChip(ExampleType type, Color color) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.15),
        borderRadius: BorderRadius.circular(4),
        border: Border.all(color: color.withValues(alpha: 0.3), width: 0.5),
      ),
      child: Text(
        type.name.toUpperCase(),
        style: TextStyle(
          fontSize: 10,
          fontWeight: FontWeight.w700,
          color: color,
          letterSpacing: 0.8,
        ),
      ),
    );
  }

  List<InlineSpan> _parseDescription(String text, {required Color highlightColor}) {
    final spans = <InlineSpan>[];
    final parts = text.split('*');
    for (int i = 0; i < parts.length; i++) {
      if (parts[i].isEmpty) continue;
      if (i.isOdd) {
        spans.add(TextSpan(
          text: parts[i],
          style: TextStyle(
            color: highlightColor,
            fontWeight: FontWeight.w600,
          ),
        ));
      } else {
        spans.add(TextSpan(
          text: parts[i],
          style: TextStyle(color: Colors.grey[400]),
        ));
      }
    }
    return spans;
  }

  @override
  void dispose() {
    exampleIndex.dispose();
    super.dispose();
  }
}
2
likes
0
points
777
downloads

Documentation

Documentation

Publisher

unverified uploader

Weekly Downloads

A Simple Flutter game engine.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on tremble